From 280f643862bffed9aae46bbffe91017b13460a01 Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:20:08 -0700 Subject: [PATCH] docs: add `Menu` module tutorials (#47761) * docs: add `Menu` module tutorials * link API docs to new tutorials * removed unreferenced fiddles * add wording for new types * fix import sort errors * delete accelerator.md * fixes Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Erick Zhao --- .markdownlint-cli2.jsonc | 4 +- docs/api/accelerator.md | 82 ---- docs/api/dock.md | 12 +- docs/api/global-shortcut.md | 11 +- docs/api/menu-item.md | 89 +---- docs/api/menu.md | 339 ++-------------- docs/api/structures/keyboard-input-event.md | 4 +- docs/api/tray.md | 49 ++- docs/api/web-frame.md | 2 +- .../keyboard-shortcuts/global/index.html | 12 - .../keyboard-shortcuts/global/main.js | 28 -- .../keyboard-shortcuts/local/index.html | 12 - .../features/keyboard-shortcuts/local/main.js | 36 -- .../features/macos-dock-menu/index.html | 12 - docs/fiddles/features/macos-dock-menu/main.js | 40 -- .../fiddles/menus/context-menu/dom/index.html | 14 + docs/fiddles/menus/context-menu/dom/main.js | 39 ++ .../fiddles/menus/context-menu/dom/preload.js | 11 + .../context-menu/web-contents/index.html | 14 + .../menus/context-menu/web-contents/main.js | 32 ++ docs/fiddles/menus/customize-menus/index.html | 124 ------ docs/fiddles/menus/customize-menus/main.js | 368 ------------------ docs/fiddles/menus/customize-menus/preload.js | 5 - .../fiddles/menus/customize-menus/renderer.js | 6 - docs/fiddles/menus/dock-menu/index.html | 18 + docs/fiddles/menus/dock-menu/main.js | 46 +++ docs/fiddles/menus/dock-menu/renderer.js | 1 + docs/fiddles/menus/tray-menu/index.html | 19 + docs/fiddles/menus/tray-menu/main.js | 60 +++ docs/fiddles/native-ui/tray/index.html | 47 --- docs/fiddles/native-ui/tray/main.js | 18 - docs/tutorial/application-menu.md | 208 ++++++++++ docs/tutorial/context-menu.md | 69 ++++ docs/tutorial/keyboard-shortcuts.md | 299 +++++++------- docs/tutorial/macos-dock.md | 109 +++--- docs/tutorial/menus.md | 348 +++++++++++++++++ docs/tutorial/tray.md | 123 +++--- docs/tutorial/window-customization.md | 1 - filenames.auto.gni | 1 - 39 files changed, 1240 insertions(+), 1472 deletions(-) delete mode 100644 docs/api/accelerator.md delete mode 100644 docs/fiddles/features/keyboard-shortcuts/global/index.html delete mode 100644 docs/fiddles/features/keyboard-shortcuts/global/main.js delete mode 100644 docs/fiddles/features/keyboard-shortcuts/local/index.html delete mode 100644 docs/fiddles/features/keyboard-shortcuts/local/main.js delete mode 100644 docs/fiddles/features/macos-dock-menu/index.html delete mode 100644 docs/fiddles/features/macos-dock-menu/main.js create mode 100644 docs/fiddles/menus/context-menu/dom/index.html create mode 100644 docs/fiddles/menus/context-menu/dom/main.js create mode 100644 docs/fiddles/menus/context-menu/dom/preload.js create mode 100644 docs/fiddles/menus/context-menu/web-contents/index.html create mode 100644 docs/fiddles/menus/context-menu/web-contents/main.js delete mode 100644 docs/fiddles/menus/customize-menus/index.html delete mode 100644 docs/fiddles/menus/customize-menus/main.js delete mode 100644 docs/fiddles/menus/customize-menus/preload.js delete mode 100644 docs/fiddles/menus/customize-menus/renderer.js create mode 100644 docs/fiddles/menus/dock-menu/index.html create mode 100644 docs/fiddles/menus/dock-menu/main.js create mode 100644 docs/fiddles/menus/dock-menu/renderer.js create mode 100644 docs/fiddles/menus/tray-menu/index.html create mode 100644 docs/fiddles/menus/tray-menu/main.js delete mode 100644 docs/fiddles/native-ui/tray/index.html delete mode 100644 docs/fiddles/native-ui/tray/main.js create mode 100644 docs/tutorial/application-menu.md create mode 100644 docs/tutorial/context-menu.md create mode 100644 docs/tutorial/menus.md diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 11be56cea63f..0e0b2c199cc7 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -21,7 +21,9 @@ "ul", "unknown", "Tabs", - "TabItem" + "TabItem", + "DocCardList", + "kbd" ] }, "no-newline-in-links": true diff --git a/docs/api/accelerator.md b/docs/api/accelerator.md deleted file mode 100644 index caefba6488f9..000000000000 --- a/docs/api/accelerator.md +++ /dev/null @@ -1,82 +0,0 @@ -# Accelerator - -> Define keyboard shortcuts. - -Accelerators are strings that can contain multiple modifiers and a single key code, -combined by the `+` character, and are used to define keyboard shortcuts -throughout your application. Accelerators are case insensitive. - -Examples: - -* `CommandOrControl+A` -* `CommandOrControl+Shift+Z` - -Shortcuts are registered with the [`globalShortcut`](global-shortcut.md) module -using the [`register`](global-shortcut.md#globalshortcutregisteraccelerator-callback) -method, i.e. - -```js -const { app, globalShortcut } = require('electron') - -app.whenReady().then(() => { - // Register a 'CommandOrControl+Y' shortcut listener. - globalShortcut.register('CommandOrControl+Y', () => { - // Do stuff when Y and either Command/Control is pressed. - }) -}) -``` - -## Platform notice - -On Linux and Windows, the `Command` key does not have any effect so -use `CommandOrControl` which represents `Command` on macOS and `Control` on -Linux and Windows to define some accelerators. - -Use `Alt` instead of `Option`. The `Option` key only exists on macOS, whereas -the `Alt` key is available on all platforms. - -The `Super` (or `Meta`) key is mapped to the `Windows` key on Windows and Linux and -`Cmd` on macOS. - -## Available modifiers - -* `Command` (or `Cmd` for short) -* `Control` (or `Ctrl` for short) -* `CommandOrControl` (or `CmdOrCtrl` for short) -* `Alt` -* `Option` -* `AltGr` -* `Shift` -* `Super` -* `Meta` - -## Available key codes - -* `0` to `9` -* `A` to `Z` -* `F1` to `F24` -* Various Punctuation: `)`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `:`, `;`, `:`, `+`, `=`, `<`, `,`, `_`, `-`, `>`, `.`, `?`, `/`, `~`, `` ` ``, `{`, `]`, `[`, `|`, `\`, `}`, `"` -* `Plus` -* `Space` -* `Tab` -* `Capslock` -* `Numlock` -* `Scrolllock` -* `Backspace` -* `Delete` -* `Insert` -* `Return` (or `Enter` as alias) -* `Up`, `Down`, `Left` and `Right` -* `Home` and `End` -* `PageUp` and `PageDown` -* `Escape` (or `Esc` for short) -* `VolumeUp`, `VolumeDown` and `VolumeMute` -* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` -* `PrintScreen` -* NumPad Keys - * `num0` - `num9` - * `numdec` - decimal key - * `numadd` - numpad `+` key - * `numsub` - numpad `-` key - * `nummult` - numpad `*` key - * `numdiv` - numpad `÷` key diff --git a/docs/api/dock.md b/docs/api/dock.md index 691e6c8842b5..7a59e647bd93 100644 --- a/docs/api/dock.md +++ b/docs/api/dock.md @@ -5,13 +5,8 @@ Process: [Main](../glossary.md#main-process)
_This class is not exported from the `'electron'` module. It is only available as a return value of other methods in the Electron API._ -The following example shows how to bounce your icon on the dock. - -```js -const { app } = require('electron') - -app.dock?.bounce() -``` +> [!TIP] +> See also: [A detailed guide about how to implement Dock menus](../tutorial/macos-dock.md). ### Instance Methods @@ -50,6 +45,9 @@ Bounces the Downloads stack if the filePath is inside the Downloads folder. Sets the string to be displayed in the dock’s badging area. +> [!IMPORTANT] +> You need to ensure that your application has the permission to display notifications for this method to work. + #### `dock.getBadge()` _macOS_ Returns `string` - The badge string of the dock. diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index 50a5dacdfd24..e6111dab43f4 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -46,13 +46,16 @@ app.on('will-quit', () => { }) ``` +> [!TIP] +> See also: [A detailed guide on Keyboard Shortcuts](../tutorial/keyboard-shortcuts.md). + ## Methods The `globalShortcut` module has the following methods: ### `globalShortcut.register(accelerator, callback)` -* `accelerator` [Accelerator](accelerator.md) +* `accelerator` string - An [accelerator](../tutorial/keyboard-shortcuts.md#accelerators) shortcut. * `callback` Function Returns `boolean` - Whether or not the shortcut was registered successfully. @@ -74,7 +77,7 @@ the app has been authorized as a [trusted accessibility client](https://develope ### `globalShortcut.registerAll(accelerators, callback)` -* `accelerators` [Accelerator](accelerator.md)[] - an array of [Accelerator](accelerator.md)s. +* `accelerators` string[] - An array of [accelerator](../tutorial/keyboard-shortcuts.md#accelerators) shortcuts. * `callback` Function Registers a global shortcut of all `accelerator` items in `accelerators`. The `callback` is called when any of the registered shortcuts are pressed by the user. @@ -93,7 +96,7 @@ the app has been authorized as a [trusted accessibility client](https://develope ### `globalShortcut.isRegistered(accelerator)` -* `accelerator` [Accelerator](accelerator.md) +* `accelerator` string - An [accelerator](../tutorial/keyboard-shortcuts.md#accelerators) shortcut. Returns `boolean` - Whether this application has registered `accelerator`. @@ -103,7 +106,7 @@ don't want applications to fight for global shortcuts. ### `globalShortcut.unregister(accelerator)` -* `accelerator` [Accelerator](accelerator.md) +* `accelerator` string - An [accelerator](../tutorial/keyboard-shortcuts.md#accelerators) shortcut. Unregisters the global shortcut of `accelerator`. diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 980b27d2dee3..2c5ee27da4e4 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -19,7 +19,7 @@ See [`Menu`](menu.md) for examples. * `window` [BaseWindow](base-window.md) | undefined - This will not be defined if no window is open. * `event` [KeyboardEvent](structures/keyboard-event.md) * `role` string (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `toggleSpellChecker`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `showSubstitutions`, `toggleSmartQuotes`, `toggleSmartDashes`, `toggleTextReplacement`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `shareMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `showAllTabs`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the - `click` property will be ignored. See [roles](#roles). + `click` property will be ignored. See [roles](../tutorial/menus.md#roles). * `type` string (optional) * `normal` * `separator` @@ -31,7 +31,7 @@ See [`Menu`](menu.md) for examples. * `label` string (optional) * `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4 * `toolTip` string (optional) _macOS_ - Hover text for this menu item. - * `accelerator` [Accelerator](accelerator.md) (optional) + * `accelerator` string (optional) - An [Accelerator](../tutorial/keyboard-shortcuts.md#accelerators) string. * `icon` ([NativeImage](native-image.md) | string) (optional) * `enabled` boolean (optional) - If false, the menu item will be greyed out and unclickable. @@ -64,88 +64,13 @@ See [`Menu`](menu.md) for examples. > [!NOTE] > `acceleratorWorksWhenHidden` is specified as being macOS-only because accelerators always work when items are hidden on Windows and Linux. The option is exposed to users to give them the option to turn it off, as this is possible in native macOS development. -### Roles - -Roles allow menu items to have predefined behaviors. - -It is best to specify `role` for any menu item that matches a standard role, -rather than trying to manually implement the behavior in a `click` function. -The built-in `role` behavior will give the best native experience. - -The `label` and `accelerator` values are optional when using a `role` and will -default to appropriate values for each platform. - -Every menu item must have either a `role`, `label`, or in the case of a separator -a `type`. - -The `role` property can have following values: - -* `undo` -* `about` - Trigger a native about panel (custom message box on Window, which does not provide its own). -* `redo` -* `cut` -* `copy` -* `paste` -* `pasteAndMatchStyle` -* `selectAll` -* `delete` -* `minimize` - Minimize current window. -* `close` - Close current window. -* `quit` - Quit the application. -* `reload` - Reload the current window. -* `forceReload` - Reload the current window ignoring the cache. -* `toggleDevTools` - Toggle developer tools in the current window. -* `togglefullscreen` - Toggle full screen mode on the current window. -* `resetZoom` - Reset the focused page's zoom level to the original size. -* `zoomIn` - Zoom in the focused page by 10%. -* `zoomOut` - Zoom out the focused page by 10%. -* `toggleSpellChecker` - Enable/disable builtin spell checker. -* `fileMenu` - Whole default "File" menu (Close / Quit) -* `editMenu` - Whole default "Edit" menu (Undo, Copy, etc.). -* `viewMenu` - Whole default "View" menu (Reload, Toggle Developer Tools, etc.) -* `windowMenu` - Whole default "Window" menu (Minimize, Zoom, etc.). - -The following additional roles are available on _macOS_: - -* `appMenu` - Whole default "App" menu (About, Services, etc.) -* `hide` - Map to the `hide` action. -* `hideOthers` - Map to the `hideOtherApplications` action. -* `unhide` - Map to the `unhideAllApplications` action. -* `showSubstitutions` - Map to the `orderFrontSubstitutionsPanel` action. -* `toggleSmartQuotes` - Map to the `toggleAutomaticQuoteSubstitution` action. -* `toggleSmartDashes` - Map to the `toggleAutomaticDashSubstitution` action. -* `toggleTextReplacement` - Map to the `toggleAutomaticTextReplacement` action. -* `startSpeaking` - Map to the `startSpeaking` action. -* `stopSpeaking` - Map to the `stopSpeaking` action. -* `front` - Map to the `arrangeInFront` action. -* `zoom` - Map to the `performZoom` action. -* `toggleTabBar` - Map to the `toggleTabBar` action. -* `selectNextTab` - Map to the `selectNextTab` action. -* `selectPreviousTab` - Map to the `selectPreviousTab` action. -* `showAllTabs` - Map to the `showAllTabs` action. -* `mergeAllWindows` - Map to the `mergeAllWindows` action. -* `moveTabToNewWindow` - Map to the `moveTabToNewWindow` action. -* `window` - The submenu is a "Window" menu. -* `help` - The submenu is a "Help" menu. -* `services` - The submenu is a ["Services"](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) menu. This is only intended for use in the Application Menu and is _not_ the same as the "Services" submenu used in context menus in macOS apps, which is not implemented in Electron. -* `recentDocuments` - The submenu is an "Open Recent" menu. -* `clearRecentDocuments` - Map to the `clearRecentDocuments` action. -* `shareMenu` - The submenu is [share menu][ShareMenu]. The `sharingItem` property must also be set to indicate the item to share. - -When specifying a `role` on macOS, `label` and `accelerator` are the only -options that will affect the menu item. All other options will be ignored. -Lowercase `role`, e.g. `toggledevtools`, is still supported. - -> [!NOTE] -> The `enabled` and `visibility` properties are not available for top-level menu items in the tray on macOS. - ### Instance Properties The following properties are available on instances of `MenuItem`: #### `menuItem.id` -A `string` indicating the item's unique id, this property can be +A `string` indicating the item's unique id. This property can be dynamically changed. #### `menuItem.label` @@ -203,17 +128,17 @@ A `string` indicating the item's hover text. #### `menuItem.enabled` -A `boolean` indicating whether the item is enabled, this property can be +A `boolean` indicating whether the item is enabled. This property can be dynamically changed. #### `menuItem.visible` -A `boolean` indicating whether the item is visible, this property can be +A `boolean` indicating whether the item is visible. This property can be dynamically changed. #### `menuItem.checked` -A `boolean` indicating whether the item is checked, this property can be +A `boolean` indicating whether the item is checked. This property can be dynamically changed. A `checkbox` menu item will toggle the `checked` property on and off when @@ -244,5 +169,3 @@ A `number` indicating an item's sequential unique id. #### `menuItem.menu` A `Menu` that the item is a part of. - -[ShareMenu]: https://developer.apple.com/design/human-interface-guidelines/macos/extensions/share-extensions/ diff --git a/docs/api/menu.md b/docs/api/menu.md index 9ba6ce00242c..5f814b19afa7 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -6,6 +6,9 @@ Process: [Main](../glossary.md#main-process) +> [!TIP] +> See also: [A detailed guide about how to implement menus in your application](../tutorial/menus.md). + > [!WARNING] > Electron's built-in classes cannot be subclassed in user code. > For more information, see [the FAQ](../faq.md#class-inheritance-does-not-work-with-electron-built-in-modules). @@ -20,7 +23,7 @@ The `Menu` class has the following static methods: #### `Menu.setApplicationMenu(menu)` -* `menu` Menu | null +- `menu` Menu | null Sets `menu` as the application menu on macOS. On Windows and Linux, the `menu` will be set as each window's top menu. @@ -51,18 +54,18 @@ Returns `Menu | null` - The application menu, if set, or `null`, if not set. #### `Menu.sendActionToFirstResponder(action)` _macOS_ -* `action` string +- `action` string Sends the `action` to the first responder of application. This is used for emulating default macOS menu behaviors. Usually you would use the -[`role`](menu-item.md#roles) property of a [`MenuItem`](menu-item.md). +[`role`](../tutorial/menus.md#roles) property of a [`MenuItem`](menu-item.md). See the [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) for more information on macOS' native actions. #### `Menu.buildFromTemplate(template)` -* `template` (MenuItemConstructorOptions | MenuItem)[] +- `template` (MenuItemConstructorOptions | MenuItem)[] Returns `Menu` @@ -77,47 +80,50 @@ The `menu` object has the following instance methods: #### `menu.popup([options])` -* `options` Object (optional) - * `window` [BaseWindow](base-window.md) (optional) - Default is the focused window. - * `frame` [WebFrameMain](web-frame-main.md) (optional) - Provide the relevant frame +- `options` Object (optional) + - `window` [BaseWindow](base-window.md) (optional) - Default is the focused window. + - `frame` [WebFrameMain](web-frame-main.md) (optional) - Provide the relevant frame if you want certain OS-level features such as Writing Tools on macOS to function correctly. Typically, this should be `params.frame` from the [`context-menu` event](web-contents.md#event-context-menu) on a WebContents, or the [`focusedFrame` property](web-contents.md#contentsfocusedframe-readonly) of a WebContents. - * `x` number (optional) - Default is the current mouse cursor position. + - `x` number (optional) - Default is the current mouse cursor position. Must be declared if `y` is declared. - * `y` number (optional) - Default is the current mouse cursor position. + - `y` number (optional) - Default is the current mouse cursor position. Must be declared if `x` is declared. - * `positioningItem` number (optional) _macOS_ - The index of the menu item to + - `positioningItem` number (optional) _macOS_ - The index of the menu item to be positioned under the mouse cursor at the specified coordinates. Default is -1. - * `sourceType` string (optional) _Windows_ _Linux_ - This should map to the `menuSourceType` + - `sourceType` string (optional) _Windows_ _Linux_ - This should map to the `menuSourceType` provided by the `context-menu` event. It is not recommended to set this value manually, only provide values you receive from other APIs or leave it `undefined`. Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`, `longPress`, `longTap`, `touchHandle`, `stylus`, `adjustSelection`, or `adjustSelectionReset`. - * `callback` Function (optional) - Called when menu is closed. + - `callback` Function (optional) - Called when menu is closed. Pops up this menu as a context menu in the [`BaseWindow`](base-window.md). +> [!TIP] +> For more details, see the [Context Menu](../tutorial/context-menu.md) guide. + #### `menu.closePopup([window])` -* `window` [BaseWindow](base-window.md) (optional) - Default is the focused window. +- `window` [BaseWindow](base-window.md) (optional) - Default is the focused window. Closes the context menu in the `window`. #### `menu.append(menuItem)` -* `menuItem` [MenuItem](menu-item.md) +- `menuItem` [MenuItem](menu-item.md) Appends the `menuItem` to the menu. #### `menu.getMenuItemById(id)` -* `id` string +- `id` string Returns `MenuItem | null` the item with the specified `id` #### `menu.insert(pos, menuItem)` -* `pos` Integer -* `menuItem` [MenuItem](menu-item.md) +- `pos` Integer +- `menuItem` [MenuItem](menu-item.md) Inserts the `menuItem` to the `pos` position of the menu. @@ -133,7 +139,7 @@ Objects created with `new Menu` or returned by `Menu.buildFromTemplate` emit the Returns: -* `event` Event +- `event` Event Emitted when `menu.popup()` is called. @@ -141,7 +147,7 @@ Emitted when `menu.popup()` is called. Returns: -* `event` Event +- `event` Event Emitted when a popup is closed either manually or with `menu.closePopup()`. @@ -153,296 +159,5 @@ Emitted when a popup is closed either manually or with `menu.closePopup()`. A `MenuItem[]` array containing the menu's items. -Each `Menu` consists of multiple [`MenuItem`](menu-item.md)s and each `MenuItem` -can have a submenu. - -## Examples - -An example of creating the application menu with the simple template API: - -```js @ts-expect-error=[107] -const { app, Menu } = require('electron') - -const isMac = process.platform === 'darwin' - -const template = [ - // { role: 'appMenu' } - ...(isMac - ? [{ - label: app.name, - submenu: [ - { role: 'about' }, - { type: 'separator' }, - { role: 'services' }, - { type: 'separator' }, - { role: 'hide' }, - { role: 'hideOthers' }, - { role: 'unhide' }, - { type: 'separator' }, - { role: 'quit' } - ] - }] - : []), - // { role: 'fileMenu' } - { - label: 'File', - submenu: [ - isMac ? { role: 'close' } : { role: 'quit' } - ] - }, - // { role: 'editMenu' } - { - label: 'Edit', - submenu: [ - { role: 'undo' }, - { role: 'redo' }, - { type: 'separator' }, - { role: 'cut' }, - { role: 'copy' }, - { role: 'paste' }, - ...(isMac - ? [ - { role: 'pasteAndMatchStyle' }, - { role: 'delete' }, - { role: 'selectAll' }, - { type: 'separator' }, - { - label: 'Speech', - submenu: [ - { role: 'startSpeaking' }, - { role: 'stopSpeaking' } - ] - } - ] - : [ - { role: 'delete' }, - { type: 'separator' }, - { role: 'selectAll' } - ]) - ] - }, - // { role: 'viewMenu' } - { - label: 'View', - submenu: [ - { role: 'reload' }, - { role: 'forceReload' }, - { role: 'toggleDevTools' }, - { type: 'separator' }, - { role: 'resetZoom' }, - { role: 'zoomIn' }, - { role: 'zoomOut' }, - { type: 'separator' }, - { role: 'togglefullscreen' } - ] - }, - // { role: 'windowMenu' } - { - label: 'Window', - submenu: [ - { role: 'minimize' }, - { role: 'zoom' }, - ...(isMac - ? [ - { type: 'separator' }, - { role: 'front' }, - { type: 'separator' }, - { role: 'window' } - ] - : [ - { role: 'close' } - ]) - ] - }, - { - role: 'help', - submenu: [ - { - label: 'Learn More', - click: async () => { - const { shell } = require('electron') - await shell.openExternal('https://electronjs.org') - } - } - ] - } -] - -const menu = Menu.buildFromTemplate(template) -Menu.setApplicationMenu(menu) -``` - -### Render process - -To create menus initiated by the renderer process, send the required -information to the main process using IPC and have the main process display the -menu on behalf of the renderer. - -Below is an example of showing a menu when the user right clicks the page: - -```js @ts-expect-error=[21] -// renderer -window.addEventListener('contextmenu', (e) => { - e.preventDefault() - ipcRenderer.send('show-context-menu') -}) - -ipcRenderer.on('context-menu-command', (e, command) => { - // ... -}) - -// main -ipcMain.on('show-context-menu', (event) => { - const template = [ - { - label: 'Menu Item 1', - click: () => { event.sender.send('context-menu-command', 'menu-item-1') } - }, - { type: 'separator' }, - { label: 'Menu Item 2', type: 'checkbox', checked: true } - ] - const menu = Menu.buildFromTemplate(template) - menu.popup({ window: BrowserWindow.fromWebContents(event.sender) }) -}) -``` - -## Notes on macOS Application Menu - -macOS has a completely different style of application menu from Windows and -Linux. Here are some notes on making your app's menu more native-like. - -### Standard Menus - -On macOS there are many system-defined standard menus, like the [`Services`](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) and -`Windows` menus. To make your menu a standard menu, you should set your menu's -`role` to one of the following and Electron will recognize them and make them -become standard menus: - -* `window` -* `help` -* `services` - -### Standard Menu Item Actions - -macOS has provided standard actions for some menu items, like `About xxx`, -`Hide xxx`, and `Hide Others`. To set the action of a menu item to a standard -action, you should set the `role` attribute of the menu item. - -### Main Menu's Name - -On macOS the label of the application menu's first item is always your app's -name, no matter what label you set. To change it, modify your app bundle's -`Info.plist` file. See -[About Information Property List Files][AboutInformationPropertyListFiles] -for more information. - -### Menu Sublabels - -Menu sublabels, or [subtitles](https://developer.apple.com/documentation/appkit/nsmenuitem/subtitle?language=objc), can be added to menu items using the `sublabel` option. Below is an example based on the renderer example above: - -```js @ts-expect-error=[12] -// main -ipcMain.on('show-context-menu', (event) => { - const template = [ - { - label: 'Menu Item 1', - sublabel: 'Subtitle 1', - click: () => { event.sender.send('context-menu-command', 'menu-item-1') } - }, - { type: 'separator' }, - { label: 'Menu Item 2', sublabel: 'Subtitle 2', type: 'checkbox', checked: true } - ] - const menu = Menu.buildFromTemplate(template) - menu.popup({ window: BrowserWindow.fromWebContents(event.sender) }) -}) -``` - -## Setting Menu for Specific Browser Window (_Linux_ _Windows_) - -The [`setMenu` method][setMenu] of browser windows can set the menu of certain -browser windows. - -## Menu Item Position - -You can make use of `before`, `after`, `beforeGroupContaining`, `afterGroupContaining` and `id` to control how the item will be placed when building a menu with `Menu.buildFromTemplate`. - -* `before` - Inserts this item before the item with the specified id. If the - referenced item doesn't exist the item will be inserted at the end of - the menu. Also implies that the menu item in question should be placed in the same “group” as the item. -* `after` - Inserts this item after the item with the specified id. If the - referenced item doesn't exist the item will be inserted at the end of - the menu. Also implies that the menu item in question should be placed in the same “group” as the item. -* `beforeGroupContaining` - Provides a means for a single context menu to declare - the placement of their containing group before the containing group of the item with the specified id. -* `afterGroupContaining` - Provides a means for a single context menu to declare - the placement of their containing group after the containing group of the item with the specified id. - -By default, items will be inserted in the order they exist in the template unless one of the specified positioning keywords is used. - -### Examples - -Template: - -```js -[ - { id: '1', label: 'one' }, - { id: '2', label: 'two' }, - { id: '3', label: 'three' }, - { id: '4', label: 'four' } -] -``` - -Menu: - -```sh -- 1 -- 2 -- 3 -- 4 -``` - -Template: - -```js -[ - { id: '1', label: 'one' }, - { type: 'separator' }, - { id: '3', label: 'three', beforeGroupContaining: ['1'] }, - { id: '4', label: 'four', afterGroupContaining: ['2'] }, - { type: 'separator' }, - { id: '2', label: 'two' } -] -``` - -Menu: - -```sh -- 3 -- 4 -- --- -- 1 -- --- -- 2 -``` - -Template: - -```js -[ - { id: '1', label: 'one', after: ['3'] }, - { id: '2', label: 'two', before: ['1'] }, - { id: '3', label: 'three' } -] -``` - -Menu: - -```sh -- --- -- 3 -- 2 -- 1 -``` - -[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html -[setMenu]: browser-window.md#winsetmenumenu-linux-windows +Each `Menu` consists of multiple [`MenuItem`](menu-item.md) instances and each `MenuItem` +can nest a `Menu` into its `submenu` property. diff --git a/docs/api/structures/keyboard-input-event.md b/docs/api/structures/keyboard-input-event.md index 3f8b4b5e8d3a..8ee819ad0ce8 100644 --- a/docs/api/structures/keyboard-input-event.md +++ b/docs/api/structures/keyboard-input-event.md @@ -2,5 +2,5 @@ * `type` string - The type of the event, can be `rawKeyDown`, `keyDown`, `keyUp` or `char`. * `keyCode` string - The character that will be sent - as the keyboard event. Should only use the valid key codes in - [Accelerator](../accelerator.md). + as the keyboard event. Should only use valid [Accelerator](../../tutorial/keyboard-shortcuts.md#accelerators) + key codes. diff --git a/docs/api/tray.md b/docs/api/tray.md index 2ffc4dc67184..dd62da0e176a 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -8,7 +8,7 @@ Process: [Main](../glossary.md#main-process) `Tray` is an [EventEmitter][event-emitter]. -```js +```js title='Creating a basic tray menu' const { app, Menu, Tray } = require('electron') let tray = null @@ -25,6 +25,9 @@ app.whenReady().then(() => { }) ``` +> [!TIP] +> See also: [A detailed guide about how to implement Tray menus](../tutorial/tray.md). + > [!WARNING] > Electron's built-in classes cannot be subclassed in user code. > For more information, see [the FAQ](../faq.md#class-inheritance-does-not-work-with-electron-built-in-modules). @@ -329,3 +332,47 @@ The `bounds` of this tray icon as `Object`. Returns `boolean` - Whether the tray icon is destroyed. [event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter + +## Platform considerations + +### Linux + +* Tray icon uses [StatusNotifierItem](https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/) + by default, when it is not available in user's desktop environment the + `GtkStatusIcon` will be used instead. +* The `click` event is emitted when the tray icon receives activation from + user, however the StatusNotifierItem spec does not specify which action would + cause an activation, for some environments it is left mouse click, but for + some it might be double left mouse click. +* In order for changes made to individual `MenuItem`s to take effect, + you have to call `setContextMenu` again. For example: + +```js +const { app, Menu, Tray } = require('electron') + +let appIcon = null +app.whenReady().then(() => { + appIcon = new Tray('/path/to/my/icon') + const contextMenu = Menu.buildFromTemplate([ + { label: 'Item1', type: 'radio' }, + { label: 'Item2', type: 'radio' } + ]) + + // Make a change to the context menu + contextMenu.items[1].checked = false + + // Call this again for Linux because we modified the context menu + appIcon.setContextMenu(contextMenu) +}) +``` + +### macOS + +* Icons passed to the Tray constructor should be [Template Images](native-image.md#template-image-macos). +* To make sure your icon isn't grainy on retina monitors, be sure your `@2x` image is 144dpi. +* If you are bundling your application (e.g., with webpack for development), be sure that the file names are not being mangled or hashed. The filename needs to end in Template, and the `@2x` image needs to have the same filename as the standard image, or MacOS will not magically invert your image's colors or use the high density image. +* 16x16 (72dpi) and 32x32@2x (144dpi) work well for most icons. + +### Windows + +* It is recommended to use `ICO` icons to get best visual effects. diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index b0a79ba5e778..00d11f41434b 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -67,7 +67,7 @@ Sets the maximum and minimum pinch-to-zoom level. > [!NOTE] > Visual zoom only applies to pinch-to-zoom behavior. Cmd+/-/0 zoom shortcuts are > controlled by the 'zoomIn', 'zoomOut', and 'resetZoom' MenuItem roles in the application -> Menu. To disable shortcuts, manually [define the Menu](./menu.md#examples) and omit zoom roles +> Menu. To disable shortcuts, manually [define the Menu](../tutorial/menus.md) and omit zoom roles > from the definition. ### `webFrame.setSpellCheckProvider(language, provider)` diff --git a/docs/fiddles/features/keyboard-shortcuts/global/index.html b/docs/fiddles/features/keyboard-shortcuts/global/index.html deleted file mode 100644 index fbe7e6323c99..000000000000 --- a/docs/fiddles/features/keyboard-shortcuts/global/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Hello World! - - - -

Hello World!

-

Hit Alt+Ctrl+I on Windows or Opt+Cmd+I on Mac to see a message printed to the console.

- - diff --git a/docs/fiddles/features/keyboard-shortcuts/global/main.js b/docs/fiddles/features/keyboard-shortcuts/global/main.js deleted file mode 100644 index 991c70d25f63..000000000000 --- a/docs/fiddles/features/keyboard-shortcuts/global/main.js +++ /dev/null @@ -1,28 +0,0 @@ -const { app, BrowserWindow, globalShortcut } = require('electron/main') - -function createWindow () { - const win = new BrowserWindow({ - width: 800, - height: 600 - }) - - win.loadFile('index.html') -} - -app.whenReady().then(() => { - globalShortcut.register('Alt+CommandOrControl+I', () => { - console.log('Electron loves global shortcuts!') - }) -}).then(createWindow) - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } -}) diff --git a/docs/fiddles/features/keyboard-shortcuts/local/index.html b/docs/fiddles/features/keyboard-shortcuts/local/index.html deleted file mode 100644 index 3aeae635b41d..000000000000 --- a/docs/fiddles/features/keyboard-shortcuts/local/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Hello World! - - - -

Hello World!

-

Hit Alt+Shift+I on Windows, or Opt+Cmd+I on mac to see a message printed to the console.

- - diff --git a/docs/fiddles/features/keyboard-shortcuts/local/main.js b/docs/fiddles/features/keyboard-shortcuts/local/main.js deleted file mode 100644 index 6393f27a22f6..000000000000 --- a/docs/fiddles/features/keyboard-shortcuts/local/main.js +++ /dev/null @@ -1,36 +0,0 @@ -const { app, BrowserWindow, Menu, MenuItem } = require('electron/main') - -function createWindow () { - const win = new BrowserWindow({ - width: 800, - height: 600 - }) - - win.loadFile('index.html') -} - -const menu = new Menu() -menu.append(new MenuItem({ - label: 'Electron', - submenu: [{ - role: 'help', - accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Alt+Shift+I', - click: () => { console.log('Electron rocks!') } - }] -})) - -Menu.setApplicationMenu(menu) - -app.whenReady().then(createWindow) - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } -}) diff --git a/docs/fiddles/features/macos-dock-menu/index.html b/docs/fiddles/features/macos-dock-menu/index.html deleted file mode 100644 index 02eb6e015a9c..000000000000 --- a/docs/fiddles/features/macos-dock-menu/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Hello World! - - - -

Hello World!

-

Right click the dock icon to see the custom menu options.

- - diff --git a/docs/fiddles/features/macos-dock-menu/main.js b/docs/fiddles/features/macos-dock-menu/main.js deleted file mode 100644 index 4b9503471bca..000000000000 --- a/docs/fiddles/features/macos-dock-menu/main.js +++ /dev/null @@ -1,40 +0,0 @@ -const { app, BrowserWindow, Menu } = require('electron/main') - -function createWindow () { - const win = new BrowserWindow({ - width: 800, - height: 600 - }) - - win.loadFile('index.html') -} - -const dockMenu = Menu.buildFromTemplate([ - { - label: 'New Window', - click () { console.log('New Window') } - }, { - label: 'New Window with Settings', - submenu: [ - { label: 'Basic' }, - { label: 'Pro' } - ] - }, - { label: 'New Command...' } -]) - -app.whenReady().then(() => { - app.dock?.setMenu(dockMenu) -}).then(createWindow) - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } -}) diff --git a/docs/fiddles/menus/context-menu/dom/index.html b/docs/fiddles/menus/context-menu/dom/index.html new file mode 100644 index 000000000000..a715fb9cb614 --- /dev/null +++ b/docs/fiddles/menus/context-menu/dom/index.html @@ -0,0 +1,14 @@ + + + + + + + Context Menu Demo + + +

Context Menu Demo

+ + + + diff --git a/docs/fiddles/menus/context-menu/dom/main.js b/docs/fiddles/menus/context-menu/dom/main.js new file mode 100644 index 000000000000..6a67caad4621 --- /dev/null +++ b/docs/fiddles/menus/context-menu/dom/main.js @@ -0,0 +1,39 @@ +// Modules to control application life and create native browser window +const { app, BrowserWindow, ipcMain, Menu } = require('electron/main') +const path = require('node:path') + +function createWindow () { + const mainWindow = new BrowserWindow({ + webPreferences: { + preload: path.join(__dirname, 'preload.js') + } + }) + + mainWindow.loadFile('index.html') + const menu = Menu.buildFromTemplate([ + { role: 'copy' }, + { role: 'cut' }, + { role: 'paste' }, + { role: 'selectall' } + ]) + + // highlight-start + ipcMain.on('context-menu', (event) => { + menu.popup({ + window: BrowserWindow.fromWebContents(event.sender) + }) + }) + // highlight-end +} + +app.whenReady().then(() => { + createWindow() + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') app.quit() +}) diff --git a/docs/fiddles/menus/context-menu/dom/preload.js b/docs/fiddles/menus/context-menu/dom/preload.js new file mode 100644 index 000000000000..fd932ebf3cb0 --- /dev/null +++ b/docs/fiddles/menus/context-menu/dom/preload.js @@ -0,0 +1,11 @@ +const { ipcRenderer } = require('electron/renderer') + +document.addEventListener('DOMContentLoaded', () => { + const textarea = document.getElementById('editable') + // highlight-start + textarea.addEventListener('contextmenu', (event) => { + event.preventDefault() + ipcRenderer.send('context-menu') + }) + // highlight-end +}) diff --git a/docs/fiddles/menus/context-menu/web-contents/index.html b/docs/fiddles/menus/context-menu/web-contents/index.html new file mode 100644 index 000000000000..8994ed2bbdc8 --- /dev/null +++ b/docs/fiddles/menus/context-menu/web-contents/index.html @@ -0,0 +1,14 @@ + + + + + + + Context Menu Demo + + +

Context Menu Demo

+ + + + diff --git a/docs/fiddles/menus/context-menu/web-contents/main.js b/docs/fiddles/menus/context-menu/web-contents/main.js new file mode 100644 index 000000000000..cc77315b6c99 --- /dev/null +++ b/docs/fiddles/menus/context-menu/web-contents/main.js @@ -0,0 +1,32 @@ +const { app, BrowserWindow, Menu } = require('electron/main') + +function createWindow () { + const win = new BrowserWindow() + // highlight-start + const menu = Menu.buildFromTemplate([ + { role: 'copy' }, + { role: 'cut' }, + { role: 'paste' }, + { role: 'selectall' } + ]) + win.webContents.on('context-menu', (_event, params) => { + // only show the context menu if the element is editable + if (params.isEditable) { + menu.popup() + } + }) + // highlight-end + win.loadFile('index.html') +} + +app.whenReady().then(() => { + createWindow() + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') app.quit() +}) diff --git a/docs/fiddles/menus/customize-menus/index.html b/docs/fiddles/menus/customize-menus/index.html deleted file mode 100644 index 1fda8f5ecd9d..000000000000 --- a/docs/fiddles/menus/customize-menus/index.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - Customize Menus - - - -
-

Customize Menus

- -

- The Menu and MenuItem modules can be used to - create custom native menus. -

- -

- There are two kinds of menus: the application (top) menu and context - (right-click) menu. -

- -

- Open the - full API documentation(opens in new window) - in your browser. -

-
- -
-

Create an application menu

-
-
-

- The Menu and MenuItem modules allow you to - customize your application menu. If you don't set any menu, Electron - will generate a minimal menu for your app by default. -

- -

- If you click the 'View' option in the application menu and then the - 'App Menu Demo', you'll see an information box displayed. -

- -
-

ProTip

- Know operating system menu differences. -

- When designing an app for multiple operating systems it's - important to be mindful of the ways application menu conventions - differ on each operating system. -

-

- For instance, on Windows, accelerators are set with an - &. Naming conventions also vary, like between - "Settings" or "Preferences". Below are resources for learning - operating system specific standards. -

- -
-
-
-
- -
-

Create a context menu

-
-
-
- -
-

- A context, or right-click, menu can be created with the - Menu and MenuItem modules as well. You can - right-click anywhere in this app or click the demo button to see an - example context menu. -

- -

- In this demo we use the ipcRenderer module to show the - context menu when explicitly calling it from the renderer process. -

-

- See the full - context-menu event documentation - for all the available properties. -

-
-
-
- - - diff --git a/docs/fiddles/menus/customize-menus/main.js b/docs/fiddles/menus/customize-menus/main.js deleted file mode 100644 index e020c3c34c23..000000000000 --- a/docs/fiddles/menus/customize-menus/main.js +++ /dev/null @@ -1,368 +0,0 @@ -// Modules to control application life and create native browser window -const { - BrowserWindow, - Menu, - MenuItem, - ipcMain, - app, - shell, - dialog, - autoUpdater -} = require('electron/main') -const path = require('node:path') - -const menu = new Menu() -menu.append(new MenuItem({ label: 'Hello' })) -menu.append(new MenuItem({ type: 'separator' })) -menu.append( - new MenuItem({ label: 'Electron', type: 'checkbox', checked: true }) -) - -const template = [ - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, - { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, - { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, - { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, - { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - } - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: (item, focusedWindow) => { - if (focusedWindow) { - // on reload, start fresh and close any old - // open secondary windows - if (focusedWindow.id === 1) { - for (const win of BrowserWindow.getAllWindows()) { - if (win.id > 1) win.close() - } - } - focusedWindow.reload() - } - } - }, - { - label: 'Toggle Full Screen', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Ctrl+Command+F' - } else { - return 'F11' - } - })(), - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) - } - } - }, - { - label: 'Toggle Developer Tools', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Alt+Command+I' - } else { - return 'Ctrl+Shift+I' - } - })(), - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.webContents.toggleDevTools() - } - } - }, - { - type: 'separator' - }, - { - label: 'App Menu Demo', - click: function (item, focusedWindow) { - if (focusedWindow) { - const options = { - type: 'info', - title: 'Application Menu Demo', - buttons: ['Ok'], - message: - 'This demo is for the Menu section, showing how to create a clickable menu item in the application menu.' - } - dialog.showMessageBox(focusedWindow, options, function () {}) - } - } - } - ] - }, - { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - { - type: 'separator' - }, - { - label: 'Reopen Window', - accelerator: 'CmdOrCtrl+Shift+T', - enabled: false, - key: 'reopenMenuItem', - click: () => { - app.emit('activate') - } - } - ] - }, - { - label: 'Help', - role: 'help', - submenu: [ - { - label: 'Learn More', - click: () => { - shell.openExternal('https://electronjs.org') - } - } - ] - } -] - -function addUpdateMenuItems (items, position) { - if (process.mas) return - - const version = app.getVersion() - const updateItems = [ - { - label: `Version ${version}`, - enabled: false - }, - { - label: 'Checking for Update', - enabled: false, - key: 'checkingForUpdate' - }, - { - label: 'Check for Update', - visible: false, - key: 'checkForUpdate', - click: () => { - autoUpdater.checkForUpdates() - } - }, - { - label: 'Restart and Install Update', - enabled: true, - visible: false, - key: 'restartToUpdate', - click: () => { - autoUpdater.quitAndInstall() - } - } - ] - - items.splice.apply(items, [position, 0].concat(updateItems)) -} - -function findReopenMenuItem () { - const menu = Menu.getApplicationMenu() - if (!menu) return - - let reopenMenuItem - for (const item of menu.items) { - if (item.submenu) { - for (const subitem of item.submenu.items) { - if (subitem.key === 'reopenMenuItem') { - reopenMenuItem = subitem - } - } - } - } - return reopenMenuItem -} - -if (process.platform === 'darwin') { - const name = app.getName() - template.unshift({ - label: name, - submenu: [ - { - label: `About ${name}`, - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: `Hide ${name}`, - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - app.quit() - } - } - ] - }) - - // Window menu. - template[3].submenu.push( - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } - ) - - addUpdateMenuItems(template[0].submenu, 1) -} - -if (process.platform === 'win32') { - const helpMenu = template[template.length - 1].submenu - addUpdateMenuItems(helpMenu, 0) -} -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - preload: path.join(__dirname, 'preload.js') - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) - - // Open external links in the default browser - mainWindow.webContents.on('will-navigate', (event, url) => { - event.preventDefault() - shell.openExternal(url) - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(() => { - createWindow() - const menu = Menu.buildFromTemplate(template) - Menu.setApplicationMenu(menu) -}) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - const reopenMenuItem = findReopenMenuItem() - if (reopenMenuItem) reopenMenuItem.enabled = true - - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -app.on('browser-window-created', (event, win) => { - const reopenMenuItem = findReopenMenuItem() - if (reopenMenuItem) reopenMenuItem.enabled = false - - win.webContents.on('context-menu', (e, params) => { - menu.popup(win, params.x, params.y) - }) -}) - -ipcMain.on('show-context-menu', event => { - const win = BrowserWindow.fromWebContents(event.sender) - menu.popup(win) -}) - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/menus/customize-menus/preload.js b/docs/fiddles/menus/customize-menus/preload.js deleted file mode 100644 index 00bc6be37da4..000000000000 --- a/docs/fiddles/menus/customize-menus/preload.js +++ /dev/null @@ -1,5 +0,0 @@ -const { contextBridge, ipcRenderer } = require('electron/renderer') - -contextBridge.exposeInMainWorld('electronAPI', { - showContextMenu: () => ipcRenderer.send('show-context-menu') -}) diff --git a/docs/fiddles/menus/customize-menus/renderer.js b/docs/fiddles/menus/customize-menus/renderer.js deleted file mode 100644 index 89ba97dcc56a..000000000000 --- a/docs/fiddles/menus/customize-menus/renderer.js +++ /dev/null @@ -1,6 +0,0 @@ -// Tell main process to show the menu when demo button is clicked -const contextMenuBtn = document.getElementById('context-menu') - -contextMenuBtn.addEventListener('click', () => { - window.electronAPI.showContextMenu() -}) diff --git a/docs/fiddles/menus/dock-menu/index.html b/docs/fiddles/menus/dock-menu/index.html new file mode 100644 index 000000000000..e52e1c2cd66c --- /dev/null +++ b/docs/fiddles/menus/dock-menu/index.html @@ -0,0 +1,18 @@ + + + + + + + Dock Menu Demo + + +

Dock Menu Demo

+ + + + diff --git a/docs/fiddles/menus/dock-menu/main.js b/docs/fiddles/menus/dock-menu/main.js new file mode 100644 index 000000000000..b88744f8c644 --- /dev/null +++ b/docs/fiddles/menus/dock-menu/main.js @@ -0,0 +1,46 @@ +const { app, BrowserWindow, Menu } = require('electron/main') +const { shell } = require('electron/common') + +function createWindow () { + const win = new BrowserWindow() + win.loadFile('index.html') +} + +function closeAllWindows () { + const wins = BrowserWindow.getAllWindows() + for (const win of wins) { + win.close() + } +} + +app.whenReady().then(() => { + createWindow() + + const dockMenu = Menu.buildFromTemplate([ + { + label: 'New Window', + click: () => { createWindow() } + }, + { + label: 'Close All Windows', + click: () => { closeAllWindows() } + }, + { + label: 'Open Electron Docs', + click: () => { + shell.openExternal('https://electronjs.org/docs') + } + } + // add more menu options to the array + ]) + + app.dock.setMenu(dockMenu) + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') app.quit() +}) diff --git a/docs/fiddles/menus/dock-menu/renderer.js b/docs/fiddles/menus/dock-menu/renderer.js new file mode 100644 index 000000000000..c6c1ab271468 --- /dev/null +++ b/docs/fiddles/menus/dock-menu/renderer.js @@ -0,0 +1 @@ +document.title = `${document.title} - ${new Date()}` diff --git a/docs/fiddles/menus/tray-menu/index.html b/docs/fiddles/menus/tray-menu/index.html new file mode 100644 index 000000000000..9f942fded15a --- /dev/null +++ b/docs/fiddles/menus/tray-menu/index.html @@ -0,0 +1,19 @@ + + + + + + + Tray Menu Demo + + +

Tray Menu Demo

+

This app will stay running even after all windows are closed.

+ + + diff --git a/docs/fiddles/menus/tray-menu/main.js b/docs/fiddles/menus/tray-menu/main.js new file mode 100644 index 000000000000..c8627e29dcf6 --- /dev/null +++ b/docs/fiddles/menus/tray-menu/main.js @@ -0,0 +1,60 @@ +const { app, BrowserWindow, Menu, Tray } = require('electron/main') +const { nativeImage } = require('electron/common') + +// save a reference to the Tray object globally to avoid garbage collection +let tray = null + +function createWindow () { + const mainWindow = new BrowserWindow() + mainWindow.loadFile('index.html') +} + +// The Tray object can only be instantiated after the 'ready' event is fired +app.whenReady().then(() => { + createWindow() + + const red = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACTSURBVHgBpZKBCYAgEEV/TeAIjuIIbdQIuUGt0CS1gW1iZ2jIVaTnhw+Cvs8/OYDJA4Y8kR3ZR2/kmazxJbpUEfQ/Dm/UG7wVwHkjlQdMFfDdJMFaACebnjJGyDWgcnZu1/lrCrl6NCoEHJBrDwEr5NrT6ko/UV8xdLAC2N49mlc5CylpYh8wCwqrvbBGLoKGvz8Bfq0QPWEUo/EAAAAASUVORK5CYII=') + const green = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACOSURBVHgBpZLRDYAgEEOrEzgCozCCGzkCbKArOIlugJvgoRAUNcLRpvGH19TkgFQWkqIohhK8UEaKwKcsOg/+WR1vX+AlA74u6q4FqgCOSzwsGHCwbKliAF89Cv89tWmOT4VaVMoVbOBrdQUz+FrD6XItzh4LzYB1HFJ9yrEkZ4l+wvcid9pTssh4UKbPd+4vED2Nd54iAAAAAElFTkSuQmCC') + + tray = new Tray(red) + tray.setToolTip('Tray Icon Demo') + + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Open App', + click: () => { + const wins = BrowserWindow.getAllWindows() + if (wins.length === 0) { + createWindow() + } else { + wins[0].focus() + } + } + }, + { + label: 'Set Green Icon', + type: 'checkbox', + click: ({ checked }) => { + checked ? tray.setImage(green) : tray.setImage(red) + } + }, + { + label: 'Set Title', + type: 'checkbox', + click: ({ checked }) => { + checked ? tray.setTitle('Title') : tray.setTitle('') + } + }, + { role: 'quit' } + ]) + + tray.setContextMenu(contextMenu) +}) + +app.on('window-all-closed', function () { + // This will prevent the app from closing when windows close +}) + +app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() +}) diff --git a/docs/fiddles/native-ui/tray/index.html b/docs/fiddles/native-ui/tray/index.html deleted file mode 100644 index 22156f39239c..000000000000 --- a/docs/fiddles/native-ui/tray/index.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - Tray - - -
-

Tray

-

- The tray module allows you to create an icon in the - operating system's notification area. -

-

This icon can also have a context menu attached.

- -

- Open the - - full API documentation - - in your browser. -

-
-
-
-

ProTip

- Tray support in Linux. -

- On Linux distributions that only have app indicator support, users - will need to install libappindicator1 to make the - tray icon work. See the - - full API documentation - - for more details about using Tray on Linux. -

-
-
- - - - - - diff --git a/docs/fiddles/native-ui/tray/main.js b/docs/fiddles/native-ui/tray/main.js deleted file mode 100644 index 2a238a265c4c..000000000000 --- a/docs/fiddles/native-ui/tray/main.js +++ /dev/null @@ -1,18 +0,0 @@ -const { app, Tray, Menu, nativeImage } = require('electron/main') - -let tray - -app.whenReady().then(() => { - const icon = nativeImage.createFromDataURL('data:image/png;base64,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') - tray = new Tray(icon) - - const contextMenu = Menu.buildFromTemplate([ - { label: 'Item1', type: 'radio' }, - { label: 'Item2', type: 'radio' }, - { label: 'Item3', type: 'radio', checked: true }, - { label: 'Item4', type: 'radio' } - ]) - - tray.setToolTip('This is my application.') - tray.setContextMenu(contextMenu) -}) diff --git a/docs/tutorial/application-menu.md b/docs/tutorial/application-menu.md new file mode 100644 index 000000000000..c77e7983ce06 --- /dev/null +++ b/docs/tutorial/application-menu.md @@ -0,0 +1,208 @@ +--- +title: Application Menu +description: Customize the main application menu for your Electron app +slug: application-menu +hide_title: true +--- + +# Application Menu + +Each Electron app has a single top-level application menu. + +* On macOS, this menu is shown in the system [menu bar](https://support.apple.com/en-ca/guide/mac-help/mchlp1446/mac). +* On Windows and Linux, this menu is shown at the top of each [BaseWindow](../api/base-window.md). + +## Building application menus + +The application menu can be set by passing a [Menu](../api/menu.md) instance into the +[`Menu.setApplicationMenu`](../api/menu.md#menusetapplicationmenumenu) static function. + +When building an application menu in Electron, each top-level array menu item **must be a submenu**. + +Electron will set a default menu for your app if this API is never called. Below is an example of +that default menu being created manually using shorthand [`MenuItem` roles](./menus.md#roles). + +```js title='Manually creating the default menu' @ts-expect-error=[107] +const { shell } = require('electron/common') +const { app, Menu } = require('electron/main') + +const isMac = process.platform === 'darwin' +const template = [ + // { role: 'appMenu' } + ...(isMac + ? [{ + label: app.name, + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + }] + : []), + // { role: 'fileMenu' } + { + label: 'File', + submenu: [ + isMac ? { role: 'close' } : { role: 'quit' } + ] + }, + // { role: 'editMenu' } + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + ...(isMac + ? [ + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' }, + { type: 'separator' }, + { + label: 'Speech', + submenu: [ + { role: 'startSpeaking' }, + { role: 'stopSpeaking' } + ] + } + ] + : [ + { role: 'delete' }, + { type: 'separator' }, + { role: 'selectAll' } + ]) + ] + }, + // { role: 'viewMenu' } + { + label: 'View', + submenu: [ + { role: 'reload' }, + { role: 'forceReload' }, + { role: 'toggleDevTools' }, + { type: 'separator' }, + { role: 'resetZoom' }, + { role: 'zoomIn' }, + { role: 'zoomOut' }, + { type: 'separator' }, + { role: 'togglefullscreen' } + ] + }, + // { role: 'windowMenu' } + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + ...(isMac + ? [ + { type: 'separator' }, + { role: 'front' }, + { type: 'separator' }, + { role: 'window' } + ] + : [ + { role: 'close' } + ]) + ] + }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click: async () => { + const { shell } = require('electron') + await shell.openExternal('https://electronjs.org') + } + } + ] + } +] + +const menu = Menu.buildFromTemplate(template) +Menu.setApplicationMenu(menu) +``` + +> [!IMPORTANT] +> On macOS, the first submenu of the application menu will **always** have your application's name +> as its label. In general, you can populate this submenu by conditionally adding a menu item with +> the `appMenu` role. + +> [!TIP] +> For additional descriptions of available roles, see the [`MenuItem` roles](./menus.md#roles) +> section of the general Menus guide. + +### Using standard OS menu roles + +Defining each submenu explicitly can get very verbose. If you want to re-use default submenus +in your app, you can use various submenu-related roles provided by Electron. + +```js title='Using default roles for each submenu' @ts-expect-error=[26] +const { shell } = require('electron/common') +const { app, Menu } = require('electron/main') + +const template = [ + ...(process.platform === 'darwin' + ? [{ role: 'appMenu' }] + : []), + { role: 'fileMenu' }, + { role: 'editMenu' }, + { role: 'viewMenu' }, + { role: 'windowMenu' }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click: async () => { + const { shell } = require('electron') + await shell.openExternal('https://electronjs.org') + } + } + ] + } +] + +const menu = Menu.buildFromTemplate(template) +Menu.setApplicationMenu(menu) +``` + +> [!NOTE] +> On macOS, the `help` role defines a top-level Help submenu that has a search bar for +> other menu items. It requires items to be added to its `submenu` to function. + +## Setting window-specific application menus _Linux_ _Windows_ + +Since the root application menu exists on each `BaseWindow` on Windows and Linux, you can override +it with a window-specific `Menu` instance via the [`win.setMenu`](../api/browser-window.md#winsetmenumenu-linux-windows) method. + +```js title='Override a window's menu' +const { BrowserWindow, Menu } = require('electron/main') + +const win = new BrowserWindow() +const menu = Menu.buildFromTemplate([ + { + label: 'my custom menu', + submenu: [ + { role: 'copy' }, + { role: 'paste' } + ] + } +]) +win.setMenu(menu) +``` + +> [!TIP] +> You can remove a specific window's application menu by calling the +> [`win.removeMenu`](../api/base-window.md#winremovemenu-linux-windows) API. diff --git a/docs/tutorial/context-menu.md b/docs/tutorial/context-menu.md new file mode 100644 index 000000000000..99563d8ef96e --- /dev/null +++ b/docs/tutorial/context-menu.md @@ -0,0 +1,69 @@ +--- +title: Context Menu +description: Configure cross-platform native OS menus with the Menu API. +slug: context-menu +hide_title: true +--- + +# Context Menu + +Context menus are pop-up menus that appear when right-clicking (or pressing a shortcut +such as Shift + F10 on Windows) somewhere in an app's interface. + +No context menu will appear by default in Electron. However, context menus can be created by using +the [`menu.popup`](../api/menu.md#menupopupoptions) function on an instance of the +[Menu](../api/menu.md) class. You will need to listen for specific context menu events and set up +the trigger for `menu.popup` manually. + +There are two ways of listening for context menu events in Electron: either via the main process +through [webContents](../api/web-contents.md) or in the renderer process via the +[`contextmenu`](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event) web event. + +## Using the `context-menu` event (main) + +Whenever a right-click is detected within the bounds of a specific `WebContents` instance, a +[`context-menu`](../api/web-contents.md#event-context-menu) event is triggered. The `params` object +passed to the listener provides an extensive list of attributes to distinguish which type of element +is receiving the event. + +For example, if you want to provide a context menu for links, check for the `linkURL` parameter. +If you want to check for editable elements such as `