8000 feat: add menu item role `palette` and `header` by gerhardberger · Pull Request #45538 · electron/electron · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: add menu item role palette and header #45538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docs/api/menu-item.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ See [`Menu`](menu.md) for examples.
* `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).
* `type` string (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or
`radio`.
* `type` string (optional)
* `normal`
* `separator`
* `submenu`
* `checkbox`
* `radio`
* `header` - Only available on macOS 14 and up.
* `palette` - Only available on macOS 14 and up.
* `label` string (optional)
* `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4
* `toolTip` string (optional) _macOS_ - Hover text for this menu item.
Expand Down Expand Up @@ -158,7 +164,10 @@ item's submenu, if present.

#### `menuItem.type`

A `string` indicating the type of the item. Can be `normal`, `separator`, `submenu`, `checkbox` or `radio`.
A `string` indicating the type of the item. Can be `normal`, `separator`, `submenu`, `checkbox`, `radio`, `header` or `palette`.

> [!NOTE]
> `header` and `palette` are only available on macOS 14 and up.

#### `menuItem.role`

Expand Down
2 changes: 1 addition & 1 deletion lib/browser/api/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const MenuItem = function (this: any, options: any) {
};
};

MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'];
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio', 'header', 'palette'];

MenuItem.prototype.getDefaultRoleAccelerator = function () {
return roles.getDefaultAccelerator(this.role);
Expand Down
5 changes: 5 additions & 0 deletions lib/browser/api/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ Menu.prototype.insert = function (pos, item) {
if (item.toolTip) this.setToolTip(pos, item.toolTip);
if (item.icon) this.setIcon(pos, item.icon);
if (item.role) this.setRole(pos, item.role);
if (item.type === 'palette' || item.type === 'header') {
this.setCustomType(pos, item.type);
}

// Make menu accessible to items.
item.overrideReadOnlyProperty('menu', this);
Expand Down Expand Up @@ -264,9 +267,11 @@ function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
const types = {
normal: () => this.insertItem(pos, item.commandId, item.label),
header: () => this.insertItem(pos, item.commandId, item.label),
checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
separator: () => this.insertSeparator(pos),
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
palette: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
radio: () => {
// Grouping radio menu items
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
Expand Down
5 changes: 5 additions & 0 deletions shell/browser/api/electron_api_menu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ void Menu::SetRole(int index, const std::u16string& role) {
model_->SetRole(index, role);
}

void Menu::SetCustomType(int index, const std::u16string& customType) {
model_->SetCustomType(index, customType);
}

void Menu::Clear() {
model_->Clear();
}
Expand Down Expand Up @@ -286,6 +290,7 @@ void Menu::FillObjectTemplate(v8::Isolate* isolate,
.SetMethod("setSublabel", &Menu::SetSublabel)
.SetMethod("setToolTip", &Menu::SetToolTip)
.SetMethod("setRole", &Menu::SetRole)
.SetMethod("setCustomType", &Menu::SetCustomType)
.SetMethod("clear", &Menu::Clear)
.SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId)
.SetMethod("getItemCount", &Menu::GetItemCount)
Expand Down
1 change: 1 addition & 0 deletions shell/browser/api/electron_api_menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class Menu : public gin::Wrappable<Menu>,
void SetSublabel(int index, const std::u16string& sublabel);
void SetToolTip(int index, const std::u16string& toolTip);
void SetRole(int index, const std::u16string& role);
void SetCustomType(int index, const std::u16string& customType);
void Clear();
int GetIndexOfCommandId(int command_id) const;
int GetItemCount() const;
Expand Down
22 changes: 19 additions & 3 deletions shell/browser/ui/cocoa/electron_menu_controller.mm 5D32
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,17 @@ - (NSMenuItem*)makeMenuItemForIndex:(NSInteger)index
}
}

std::u16string role = model->GetRoleAt(index);
electron::ElectronMenuModel::ItemType type = model->GetTypeAt(index);
std::u16string customType = model->GetCustomTypeAt(index);

// The sectionHeaderWithTitle menu item is only available in macOS 14.0+.
if (@available(macOS 14, *)) {
if (customType == u"header") {
item = [NSMenuItem sectionHeaderWithTitle:label];
}
}

// If the menu item has an icon, set it.
ui::ImageModel icon = model->GetIconAt(index);
if (icon.IsImage())
Expand All @@ -338,9 +349,6 @@ - (NSMenuItem*)makeMenuItemForIndex:(NSInteger)index
std::u16string toolTip = model->GetToolTipAt(index);
[item setToolTip:base::SysUTF16ToNSString(toolTip)];

std::u16string role = model->GetRoleAt(index);
electron::ElectronMenuModel::ItemType type = model->GetTypeAt(index);

if (role == u"services") {
std::u16string title = u"Services";
NSString* sub_label = l10n_util::FixUpWindowsStyleLabel(title);
Expand Down Expand Up @@ -372,6 +380,14 @@ - (NSMenuItem*)makeMenuItemForIndex:(NSInteger)index
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
? [self menuFromModel:submenuModel]
: MakeEmptySubmenu();

// NSMenuPresentationStylePalette is only available in macOS 14.0+.
if (@available(macOS 14, *)) {
if (customType == u"palette") {
submenu.presentationStyle = NSMenuPresentationStylePalette;
}
}

[submenu setTitle:[item title]];
[item setSubmenu:submenu];

Expand Down
12 changes: 12 additions & 0 deletions shell/browser/ui/electron_menu_model.cc
F438
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ std::u16string ElectronMenuModel::GetToolTipAt(size_t index) {
return iter == std::end(toolTips_) ? std::u16string() : iter->second;
}

void ElectronMenuModel::SetCustomType(size_t index,
const std::u16string& customType) {
int command_id = GetCommandIdAt(index);
customTypes_[command_id] = customType;
}

std::u16string ElectronMenuModel::GetCustomTypeAt(size_t index) {
const int command_id = GetCommandIdAt(index);
const auto iter = customTypes_.find(command_id);
return iter == std::end(customTypes_) ? std::u16string() : iter->second;
}

void ElectronMenuModel::SetRole(size_t index, const std::u16string& role) {
int command_id = GetCommandIdAt(index);
roles_[command_id] = role;
Expand Down
4 changes: 4 additions & 0 deletions shell/browser/ui/electron_menu_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class ElectronMenuModel : public ui::SimpleMenuModel {

void SetToolTip(size_t index, const std::u16string& toolTip);
std::u16string GetToolTipAt(size_t index);
void SetCustomType(size_t index, const std::u16string& customType);
std::u16string GetCustomTypeAt(size_t index);
void SetRole(size_t index, const std::u16string& role);
std::u16string GetRoleAt(size_t index);
void SetSecondaryLabel(size_t index, const std::u16string& sublabel);
Expand Down Expand Up @@ -125,6 +127,8 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
base::flat_map<int, std::u16string> toolTips_; // command id -> tooltip
base::flat_map<int, std::u16string> roles_; // command id -> role
base::flat_map<int, std::u16string> sublabels_; // command id -> sublabel
base::flat_map<int, std::u16string>
customTypes_; // command id -> custom type
base::ObserverList<Observer> observers_;

base::WeakPtrFactory<ElectronMenuModel> weak_factory_{this};
Expand Down
2 changes: 1 addition & 1 deletion typings/internal-ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ declare module NodeJS {
interface ContextMenuItem {
id: number;
label: string;
type: 'normal' | 'separator' | 'subMenu' | 'checkbox';
type: 'normal' | 'separator' | 'subMenu' | 'checkbox' | 'header' | 'palette';
checked: boolean;
enabled: boolean;
subItems: ContextMenuItem[];
Expand Down
1 change: 1 addition & 0 deletions typings/internal-electron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ declare namespace Electron {
setToolTip(index: number, tooltip: string): void;
setIcon(index: number, image: string | NativeImage): void;
setRole(index: number, role: string): void;
setCustomType(index: number, customType: string): void;
insertItem(index: number, commandId: number, label: string): void;
insertCheckItem(index: number, commandId: number, label: string): void;
insertRadioItem(index: number, commandId: number, label: string, groupId: number): void;
Expand Down
0