feat: add menu item role palette
and header
(#47245)
* feat: add menu item role `palette` and `header` Co-authored-by: Gellert Hegyi <gellihegyi@gmail.com> * adds comments Co-authored-by: Gellert Hegyi <gellihegyi@gmail.com> * refactors new role items to new item types Co-authored-by: Gellert Hegyi <gellert@poolside.ai> * docs: custom type Co-authored-by: Samuel Maddock <smaddock@slack-corp.com> * docs: note types only available on mac 14+ Co-authored-by: Samuel Maddock <smaddock@slack-corp.com> --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Gellert Hegyi <gellihegyi@gmail.com> Co-authored-by: Gellert Hegyi <gellert@poolside.ai> Co-authored-by: Samuel Maddock <smaddock@slack-corp.com>
This commit is contained in:
parent
9e453030ae
commit
713030b21a
10 changed files with 61 additions and 8 deletions
|
@ -20,8 +20,14 @@ See [`Menu`](menu.md) for examples.
|
||||||
* `event` [KeyboardEvent](structures/keyboard-event.md)
|
* `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
|
* `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](#roles).
|
||||||
* `type` string (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or
|
* `type` string (optional)
|
||||||
`radio`.
|
* `normal`
|
||||||
|
* `separator`
|
||||||
|
* `submenu`
|
||||||
|
* `checkbox`
|
||||||
|
* `radio`
|
||||||
|
* `header` - Only available on macOS 14 and up.
|
||||||
|
* `palette` - Only available on macOS 14 and up.
|
||||||
* `label` string (optional)
|
* `label` string (optional)
|
||||||
* `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4
|
* `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4
|
||||||
* `toolTip` string (optional) _macOS_ - Hover text for this menu item.
|
* `toolTip` string (optional) _macOS_ - Hover text for this menu item.
|
||||||
|
@ -162,7 +168,10 @@ item's submenu, if present.
|
||||||
|
|
||||||
#### `menuItem.type`
|
#### `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`
|
#### `menuItem.role`
|
||||||
|
|
||||||
|
|
|
@ -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 () {
|
MenuItem.prototype.getDefaultRoleAccelerator = function () {
|
||||||
return roles.getDefaultAccelerator(this.role);
|
return roles.getDefaultAccelerator(this.role);
|
||||||
|
|
|
@ -143,6 +143,9 @@ Menu.prototype.insert = function (pos, item) {
|
||||||
if (item.toolTip) this.setToolTip(pos, item.toolTip);
|
if (item.toolTip) this.setToolTip(pos, item.toolTip);
|
||||||
if (item.icon) this.setIcon(pos, item.icon);
|
if (item.icon) this.setIcon(pos, item.icon);
|
||||||
if (item.role) this.setRole(pos, item.role);
|
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.
|
// Make menu accessible to items.
|
||||||
item.overrideReadOnlyProperty('menu', this);
|
item.overrideReadOnlyProperty('menu', this);
|
||||||
|
@ -264,9 +267,11 @@ function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]
|
||||||
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
|
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
|
||||||
const types = {
|
const types = {
|
||||||
normal: () => this.insertItem(pos, item.commandId, item.label),
|
normal: () => this.insertItem(pos, item.commandId, item.label),
|
||||||
|
header: () => this.insertItem(pos, item.commandId, item.label),
|
||||||
checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
|
checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
|
||||||
separator: () => this.insertSeparator(pos),
|
separator: () => this.insertSeparator(pos),
|
||||||
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
|
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
|
||||||
|
palette: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
|
||||||
radio: () => {
|
radio: () => {
|
||||||
// Grouping radio menu items
|
// Grouping radio menu items
|
||||||
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
|
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
|
||||||
|
|
|
@ -213,6 +213,10 @@ void Menu::SetRole(int index, const std::u16string& role) {
|
||||||
model_->SetRole(index, role);
|
model_->SetRole(index, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::SetCustomType(int index, const std::u16string& customType) {
|
||||||
|
model_->SetCustomType(index, customType);
|
||||||
|
}
|
||||||
|
|
||||||
void Menu::Clear() {
|
void Menu::Clear() {
|
||||||
model_->Clear();
|
model_->Clear();
|
||||||
}
|
}
|
||||||
|
@ -286,6 +290,7 @@ void Menu::FillObjectTemplate(v8::Isolate* isolate,
|
||||||
.SetMethod("setSublabel", &Menu::SetSublabel)
|
.SetMethod("setSublabel", &Menu::SetSublabel)
|
||||||
.SetMethod("setToolTip", &Menu::SetToolTip)
|
.SetMethod("setToolTip", &Menu::SetToolTip)
|
||||||
.SetMethod("setRole", &Menu::SetRole)
|
.SetMethod("setRole", &Menu::SetRole)
|
||||||
|
.SetMethod("setCustomType", &Menu::SetCustomType)
|
||||||
.SetMethod("clear", &Menu::Clear)
|
.SetMethod("clear", &Menu::Clear)
|
||||||
.SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId)
|
.SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId)
|
||||||
.SetMethod("getItemCount", &Menu::GetItemCount)
|
.SetMethod("getItemCount", &Menu::GetItemCount)
|
||||||
|
|
|
@ -116,6 +116,7 @@ class Menu : public gin::Wrappable<Menu>,
|
||||||
void SetSublabel(int index, const std::u16string& sublabel);
|
void SetSublabel(int index, const std::u16string& sublabel);
|
||||||
void SetToolTip(int index, const std::u16string& toolTip);
|
void SetToolTip(int index, const std::u16string& toolTip);
|
||||||
void SetRole(int index, const std::u16string& role);
|
void SetRole(int index, const std::u16string& role);
|
||||||
|
void SetCustomType(int index, const std::u16string& customType);
|
||||||
void Clear();
|
void Clear();
|
||||||
int GetIndexOfCommandId(int command_id) const;
|
int GetIndexOfCommandId(int command_id) const;
|
||||||
int GetItemCount() const;
|
int GetItemCount() const;
|
||||||
|
|
|
@ -330,6 +330,17 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// If the menu item has an icon, set it.
|
||||||
ui::ImageModel icon = model->GetIconAt(index);
|
ui::ImageModel icon = model->GetIconAt(index);
|
||||||
if (icon.IsImage())
|
if (icon.IsImage())
|
||||||
|
@ -338,9 +349,6 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||||
std::u16string toolTip = model->GetToolTipAt(index);
|
std::u16string toolTip = model->GetToolTipAt(index);
|
||||||
[item setToolTip:base::SysUTF16ToNSString(toolTip)];
|
[item setToolTip:base::SysUTF16ToNSString(toolTip)];
|
||||||
|
|
||||||
std::u16string role = model->GetRoleAt(index);
|
|
||||||
electron::ElectronMenuModel::ItemType type = model->GetTypeAt(index);
|
|
||||||
|
|
||||||
if (role == u"services") {
|
if (role == u"services") {
|
||||||
std::u16string title = u"Services";
|
std::u16string title = u"Services";
|
||||||
NSString* sub_label = l10n_util::FixUpWindowsStyleLabel(title);
|
NSString* sub_label = l10n_util::FixUpWindowsStyleLabel(title);
|
||||||
|
@ -372,6 +380,14 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||||
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
|
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
|
||||||
? [self menuFromModel:submenuModel]
|
? [self menuFromModel:submenuModel]
|
||||||
: MakeEmptySubmenu();
|
: MakeEmptySubmenu();
|
||||||
|
|
||||||
|
// NSMenuPresentationStylePalette is only available in macOS 14.0+.
|
||||||
|
if (@available(macOS 14, *)) {
|
||||||
|
if (customType == u"palette") {
|
||||||
|
submenu.presentationStyle = NSMenuPresentationStylePalette;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[submenu setTitle:[item title]];
|
[submenu setTitle:[item title]];
|
||||||
[item setSubmenu:submenu];
|
[item setSubmenu:submenu];
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,18 @@ std::u16string ElectronMenuModel::GetToolTipAt(size_t index) {
|
||||||
return iter == std::end(toolTips_) ? std::u16string() : iter->second;
|
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) {
|
void ElectronMenuModel::SetRole(size_t index, const std::u16string& role) {
|
||||||
int command_id = GetCommandIdAt(index);
|
int command_id = GetCommandIdAt(index);
|
||||||
roles_[command_id] = role;
|
roles_[command_id] = role;
|
||||||
|
|
|
@ -84,6 +84,8 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
|
||||||
|
|
||||||
void SetToolTip(size_t index, const std::u16string& toolTip);
|
void SetToolTip(size_t index, const std::u16string& toolTip);
|
||||||
std::u16string GetToolTipAt(size_t index);
|
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);
|
void SetRole(size_t index, const std::u16string& role);
|
||||||
std::u16string GetRoleAt(size_t index);
|
std::u16string GetRoleAt(size_t index);
|
||||||
void SetSecondaryLabel(size_t index, const std::u16string& sublabel);
|
void SetSecondaryLabel(size_t index, const std::u16string& sublabel);
|
||||||
|
@ -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> toolTips_; // command id -> tooltip
|
||||||
base::flat_map<int, std::u16string> roles_; // command id -> role
|
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> sublabels_; // command id -> sublabel
|
||||||
|
base::flat_map<int, std::u16string>
|
||||||
|
customTypes_; // command id -> custom type
|
||||||
base::ObserverList<Observer> observers_;
|
base::ObserverList<Observer> observers_;
|
||||||
|
|
||||||
base::WeakPtrFactory<ElectronMenuModel> weak_factory_{this};
|
base::WeakPtrFactory<ElectronMenuModel> weak_factory_{this};
|
||||||
|
|
2
typings/internal-ambient.d.ts
vendored
2
typings/internal-ambient.d.ts
vendored
|
@ -279,7 +279,7 @@ declare module NodeJS {
|
||||||
interface ContextMenuItem {
|
interface ContextMenuItem {
|
||||||
id: number;
|
id: number;
|
||||||
label: string;
|
label: string;
|
||||||
type: 'normal' | 'separator' | 'subMenu' | 'checkbox';
|
type: 'normal' | 'separator' | 'subMenu' | 'checkbox' | 'header' | 'palette';
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
subItems: ContextMenuItem[];
|
subItems: ContextMenuItem[];
|
||||||
|
|
1
typings/internal-electron.d.ts
vendored
1
typings/internal-electron.d.ts
vendored
|
@ -172,6 +172,7 @@ declare namespace Electron {
|
||||||
setToolTip(index: number, tooltip: string): void;
|
setToolTip(index: number, tooltip: string): void;
|
||||||
setIcon(index: number, image: string | NativeImage): void;
|
setIcon(index: number, image: string | NativeImage): void;
|
||||||
setRole(index: number, role: string): void;
|
setRole(index: number, role: string): void;
|
||||||
|
setCustomType(index: number, customType: string): void;
|
||||||
insertItem(index: number, commandId: number, label: string): void;
|
insertItem(index: number, commandId: number, label: string): void;
|
||||||
insertCheckItem(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;
|
insertRadioItem(index: number, commandId: number, label: string, groupId: number): void;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue