also looking at this
docs(menu): delete the dead NativeMenus class, document the real menu API
#36
3 files
+188
-59
| @@ -7,6 +7,7 @@ The Craft JavaScript Bridge provides a seamless interface for your web applicati | ||
| 7 | 7 | - [Overview](#overview) |
| 8 | 8 | - [Getting Started](#getting-started) |
| 9 | 9 | - [System Tray API](#system-tray-api) |
| 10 | - [Application Menu (macOS)](#application-menu-macos) | |
| 10 | 11 | - [Window API](#window-api) |
| 11 | 12 | - [App API](#app-api) |
| 12 | 13 | - [TypeScript Support](#typescript-support) |
| @@ -157,6 +158,134 @@ interface MenuItem { | ||
| 157 | 158 | } |
| 158 | 159 | ``` |
| 159 | 160 | |
| 161 | ## Application Menu (macOS) | |
| 162 | ||
| 163 | The menu bar across the top of the screen, distinct from the tray menu above. | |
| 164 | macOS only — on Linux and Windows these calls are accepted and ignored. | |
| 165 | ||
| 166 | Craft installs a default bar at startup, so an app that never touches this API | |
| 167 | still gets the standard shortcuts. Calling `set()` replaces that bar | |
| 168 | **entirely** — if your app has text input anywhere, declare an Edit menu | |
| 169 | yourself, or Cmd+X/C/V stop reaching it. | |
| 170 | ||
| 171 | ### `window.craft.menu.set(options: { menus: MenuDefinition[] }): Promise<void>` | |
| 172 | ||
| 173 | Replace the whole bar. | |
| 174 | ||
| 175 | ```javascript | |
| 176 | await window.craft.menu.set({ | |
| 177 | menus: [ | |
| 178 | { | |
| 179 | label: 'File', | |
| 180 | items: [ | |
| 181 | { id: 'new', label: 'New', shortcut: 'cmd+n' }, | |
| 182 | { id: 'open', label: 'Open...', shortcut: 'cmd+o' }, | |
| 183 | { separator: true }, | |
| 184 | { role: 'close', label: 'Close Window', shortcut: 'cmd+w' }, | |
| 185 | ], | |
| 186 | }, | |
| 187 | { | |
| 188 | label: 'Edit', | |
| 189 | items: [ | |
| 190 | { role: 'undo', label: 'Undo', shortcut: 'cmd+z' }, | |
| 191 | { role: 'redo', label: 'Redo', shortcut: 'cmd+shift+z' }, | |
| 192 | { separator: true }, | |
| 193 | { role: 'cut', label: 'Cut', shortcut: 'cmd+x' }, | |
| 194 | { role: 'copy', label: 'Copy', shortcut: 'cmd+c' }, | |
| 195 | { role: 'paste', label: 'Paste', shortcut: 'cmd+v' }, | |
| 196 | { role: 'selectAll', label: 'Select All', shortcut: 'cmd+a' }, | |
| 197 | ], | |
| 198 | }, | |
| 199 | ], | |
| 200 | }); | |
| 201 | ``` | |
| 202 | ||
| 203 | The returned promise resolves once the message has been posted, not once the | |
| 204 | menu exists — native acknowledges nothing on success. A failure arrives on the | |
| 205 | console rather than as a rejection. | |
| 206 | ||
| 207 | ### `window.craft.menu.onAction(handler: (event: { id: string }) => void): () => void` | |
| 208 | ||
| 209 | Menu clicks arrive here. Without it there is no way to respond to a menu at all. | |
| 210 | Returns an unsubscribe function. | |
| 211 | ||
| 212 | ```javascript | |
| 213 | const off = window.craft.menu.onAction(({ id }) => { | |
| 214 | if (id === 'new') createDocument(); | |
| 215 | if (id === 'open') openDocument(); | |
| 216 | }); | |
| 217 | ``` | |
| 218 | ||
| 219 | Only items with an `id` fire this. Role items are performed by the system and | |
| 220 | never reach your handler. | |
| 221 | ||
| 222 | ### Items and roles | |
| 223 | ||
| 224 | ```typescript | |
| 225 | interface MenuDefinition { | |
| 226 | label: string | |
| 227 | items: MenuItemDefinition[] | |
| 228 | } | |
| 229 | ||
| 230 | interface MenuItemDefinition { | |
| 231 | /** Echoed back by `onAction` when this item is clicked. */ | |
| 232 | id?: string | |
| 233 | label?: string | |
| 234 | /** Accelerator, e.g. `cmd+n`, `cmd+shift+r`. */ | |
| 235 | shortcut?: string | |
| 236 | /** Icon name (SF Symbol, resolved through Craft's cross-platform icon table). */ | |
| 237 | icon?: string | |
| 238 | /** Renders a divider; every other field is ignored when this is set. */ | |
| 239 | separator?: boolean | |
| 240 | /** A standard behavior the system performs — see the table below. */ | |
| 241 | role?: string | |
| 242 | } | |
| 243 | ``` | |
| 244 | ||
| 245 | This is the whole of it. The native decoder parses exactly these fields and | |
| 246 | ignores anything else, so extra keys are dropped in silence rather than | |
| 247 | rejected. Two consequences worth knowing: | |
| 248 | ||
| 249 | - a separator is `{ separator: true }`, not `{ type: 'separator' }` | |
| 250 | - there are no nested submenus yet; the bar is one level deep | |
| 251 | ||
| 252 | Checkmarks and enablement are set after the fact, with `checkItem()` / | |
| 253 | `enableItem()`, not with a field here. | |
| 254 | ||
| 255 | **Roles.** A role item is wired to an AppKit selector through the responder | |
| 256 | chain, exactly as a hand-built Mac menu would be. Use one wherever the system | |
| 257 | already knows how to do the job: | |
| 258 | ||
| 259 | | role | performs | role | performs | | |
| 260 | |---|---|---|---| | |
| 261 | | `about` | About panel | `selectAll` | select all | | |
| 262 | | `hide` | hide app | `close` | close window | | |
| 263 | | `hideOthers` | hide other apps | `minimize` | minimize | | |
| 264 | | `showAll` | unhide all | `zoom` | zoom | | |
| 265 | | `quit` | quit | `front` | bring all to front | | |
| 266 | | `undo` / `redo` | undo, redo | `fullscreen` | toggle full screen | | |
| 267 | | `cut` / `copy` / `paste` | clipboard | `reload` | reload the webview | | |
| 268 | | `delete` | delete selection | `forceReload` | reload ignoring cache | | |
| 269 | ||
| 270 | Cut, copy and paste **must** be roles. A round trip through JavaScript cannot | |
| 271 | reach the field editor inside a text field or the WKWebView's own clipboard | |
| 272 | handling, so an `id` item named "Copy" will not copy anything. | |
| 273 | ||
| 274 | An unrecognized role degrades to an ordinary `id` item, which fires `onAction` — | |
| 275 | a newer bridge surface therefore fails soft on an older binary. | |
| 276 | ||
| 277 | ### The rest of the surface | |
| 278 | ||
| 279 | | method | effect | | |
| 280 | |---|---| | |
| 281 | | `setDock({ items })` | replace the Dock menu (right-click the Dock icon) | | |
| 282 | | `clearDock()` | remove it | | |
| 283 | | `addItem(menuId, item)` | append one item to an existing menu; `menuId` is the menu's **label**, e.g. `'File'`. Pass `index` on the item to insert instead of append | | |
| 284 | | `removeItem(itemId)` | remove by id | | |
| 285 | | `enableItem(itemId)` / `disableItem(itemId)` | greyed out or not | | |
| 286 | | `checkItem(itemId)` / `uncheckItem(itemId)` | checkmark | | |
| 287 | | `setItemLabel(itemId, label)` | rename in place | | |
| 288 | ||
| 160 | 289 | ## Window API |
| 161 | 290 | |
| 162 | 291 | Control the application window from JavaScript. |