ReviewOS

also looking at this

craft-native/craft

fix(shortcuts): make global hotkeys actually fire

#54
Merged glennmichael123 wants to merge fix/global-shortcuts-never-fire into main
11 files +2435 -417
docs/BRIDGE_API.mdmodified+82-0
Changes to docs/BRIDGE_API.md
@@ -8,6 +8,7 @@ The Craft JavaScript Bridge provides a seamless interface for your web applicati
88- [Getting Started](#getting-started)
99- [System Tray API](#system-tray-api)
1010- [Application Menu (macOS)](#application-menu-macos)
11- [Global Shortcuts (macOS)](#global-shortcuts-macos)
1112- [Window API](#window-api)
1213- [App API](#app-api)
1314- [TypeScript Support](#typescript-support)
@@ -286,6 +287,86 @@ a newer bridge surface therefore fails soft on an older binary.
286287| `checkItem(itemId)` / `uncheckItem(itemId)` | checkmark |
287288| `setItemLabel(itemId, label)` | rename in place |
288289
290## Global Shortcuts (macOS)
291
292System-wide hotkeys: they fire whether or not your app is frontmost. macOS only
293— on Linux and Windows every registration is refused, because craft has no
294implementation there and a shortcut that can never fire is worse than one that
295was never accepted.
296
297Craft registers these through Carbon's `RegisterEventHotKey`, which needs no
298Accessibility or Input Monitoring permission and only ever calls back for the
299exact combinations you asked for. Your app never sees any other keystroke.
300
301### `window.craft.shortcuts.register(id, accelerator): Promise<void>`
302
303Reserve a combination. `id` is yours to choose and is what comes back when the
304key is pressed. Registering an id twice replaces the first binding.
305
306```javascript
307await window.craft.shortcuts.register('summon', 'Cmd+Shift+H');
308
309window.craft.shortcuts.on(({ id, accelerator }) => {
310 if (id === 'summon') window.craft.window.toggle();
311});
312```
313
314Accelerators are `+`-separated and case-insensitive. The last component is the
315key; everything before it is a modifier:
316
317| Modifier | Accepted as |
318|---|---|
319| Command | `Cmd`, `Command`, `Meta`, `Super`, `⌘` |
320| Control | `Ctrl`, `Control`, `⌃` |
321| Option | `Alt`, `Option`, `Opt`, `⌥` |
322| Shift | `Shift`, `⇧` |
323| Either | `CmdOrCtrl`, `CommandOrControl`, `Mod` — command on macOS, control elsewhere |
324
325Keys are named by position on the keyboard, not by the character they produce,
326so a shortcut survives the user switching layout. Letters, digits, punctuation,
327`Space`, `Tab`, `Return`, `Escape`, `Delete`, the arrows, `Home`/`End`/`PageUp`/
328`PageDown`, `F1`–`F20` and the keypad (`Keypad0`–`Keypad9`, `KeypadEnter`, …)
329are all bindable. Web and Electron spellings — `Backspace`, `Esc`, `Enter`,
330`ArrowLeft`, `Plus` — resolve to the same keys.
331
332**At least one of Command, Control or Option is required**, except on the
333function keys. A bare global hotkey on `H` would mean no application on the
334system — including yours — ever saw the user type an h again; failing the
335registration is recoverable, and that is not. Shift alone does not count, since
336Shift+H is how you type a capital H.
337
338### `window.craft.shortcuts.onError(handler): () => void`
339
340Where a failed call arrives: `{ id, code, message }`. Every verb here except
341`isRegistered` and `list` is fire-and-forget — the promise resolves as soon as
342the message is posted, not when the key is reserved — so this is how you learn
343that a combination belongs to another app or to the system, or that `enable()`
344could not take a released key back.
345
346```javascript
347window.craft.shortcuts.onError(({ id, message }) => {
348 showToast(`Could not bind ${id}: ${message}`);
349});
350```
351
352### `window.craft.shortcuts.on(handler): () => void`
353
354Fires on every press of a registered, enabled shortcut, with `{ id, accelerator }`.
355The accelerator is craft's canonical spelling — `Cmd+Delete`, whatever you
356originally wrote — so it round-trips back through `register()`. Returns an
357unsubscribe function.
358
359### The rest
360
361| Call | What it does |
362|---|---|
363| `unregister(id)` | give the key back to the system and forget the shortcut |
364| `unregisterAll()` | the same, for every shortcut this app holds |
365| `disable(id)` | stop firing **and release the key**, so other apps can use it again; the shortcut stays listed |
366| `enable(id)` | take it back — this can fail if something else claimed it in the meantime, and reports on `onError` |
367| `isRegistered(id): Promise<boolean>` | whether the id is known, enabled or not |
368| `list(): Promise<Shortcut[]>` | `{ id, accelerator, key, enabled }` for each |
369
289370## Window API
290371
291372Control the application window from JavaScript.
@@ -519,6 +600,7 @@ async function onComplete() {
519600| Tray Menu | �� | �� | �� |
520601| Window Control | ✅ | ✅ | ✅ |
521602| Hide Dock Icon | ✅ | ➖ | ➖ |
603| Global Shortcuts | ✅ | �� | �� |
522604
523605✅ Implemented | �� In Progress | ➖ Not Applicable
524606