Keyboard Shortcuts
Headless shortcut management with registration, conflict detection, user overrides, and platform-aware display formatting.
import { createShortcutManager } from 'phoundry-ui'; Basic Registration & Display
Create a manager, register shortcuts, and display formatted key combos.
Save: ⌘ S New: ⌘ N
The manager listens to keyboard events via handleKeyDown(). Wire it up in your layout's onkeydown.
Show code
<script lang="ts">
import { createShortcutManager } from 'phoundry-ui';
const shortcuts = createShortcutManager();
shortcuts.register({
id: 'app.save',
label: 'Save',
defaults: ['CmdOrCtrl+S'],
handler: () => console.log('Saved!'),
});
shortcuts.register({
id: 'app.new',
label: 'New Document',
defaults: ['CmdOrCtrl+N'],
handler: () => console.log('New document'),
section: 'Documents',
});
// Display the formatted shortcut
const display = shortcuts.getDisplayShortcut('app.save');
// → "⌘S" on Mac, "Ctrl+S" on Windows/Linux
await shortcuts.init();
</script>Platform-Specific Shortcuts
Use per-platform objects for shortcuts that differ across operating systems.
Mac: ⌘ ⌫ Win/Linux: ⌃ ⇧ K
Show code
shortcuts.register({
id: 'editor.delete-line',
label: 'Delete Line',
defaults: [
{ mac: 'Cmd+Backspace', default: 'Ctrl+Shift+K' }
],
handler: () => deleteLine(),
});User Overrides & Conflict Detection
Users can rebind shortcuts. The manager validates conflicts and persists overrides via callbacks.
Show code
// User wants to rebind Save to Ctrl+Shift+S
await shortcuts.setOverride('app.save', ['Ctrl+Shift+S']);
// Check for conflicts before setting
const conflict = shortcuts.checkConflict('Ctrl+N', 'app.new');
if (conflict) {
console.warn(`Conflicts with "${conflict.existingActionLabel}"`);
}
// Reset a single action or all overrides
await shortcuts.resetToDefault('app.save');
await shortcuts.resetAll();Standalone Utilities
Low-level utilities for parsing, formatting, and matching shortcuts outside the manager.
Show code
import { parseShortcut, formatShortcut, matchesEvent } from 'phoundry-ui';
const parsed = parseShortcut('CmdOrCtrl+Shift+P', 'mac');
// → { key: 'p', ctrl: false, meta: true, alt: false, shift: true }
const display = formatShortcut('CmdOrCtrl+S', 'mac');
// → "⌘S"
document.addEventListener('keydown', (e) => {
if (matchesEvent(parsed, e)) {
console.log('Matched!');
}
});Factory vs getter
createShortcutManager installs the singleton; subsequent modules can call getShortcutManager() without threading instances through every import.
Show code
import { createShortcutManager, getShortcutManager } from 'phoundry-ui';
// Typically once at startup:
export const shortcuts = createShortcutManager({ loadOverrides, saveOverrides });
// Elsewhere (after startup):
const same = getShortcutManager();Construction
| Prop | Type | Default | Description |
|---|---|---|---|
| createShortcutManager(options?) | → ShortcutManager | — | Construct (or replace) the process singleton — must run before <code>getShortcutManager()</code>. |
| getShortcutManager() | → ShortcutManager | — | Throws if no manager was created yet — useful for modules that only consume shortcuts. |
ShortcutManager (state)
| Prop | Type | Default | Description |
|---|---|---|---|
| platform | ShortcutPlatform | — | Resolved platform key (<code>mac</code>, <code>windows</code>, <code>linux</code>) used when parsing definitions. |
| isReady | boolean | — | Becomes true after <code>init()</code> resolves (safe to read shortcuts with overrides applied). |
ShortcutManagerOptions
| Prop | Type | Default | Description |
|---|---|---|---|
| loadOverrides | () => Promise<ShortcutOverrides> | — | Callback to load persisted shortcut overrides at init. |
| saveOverrides | (overrides) => Promise<void> | — | Callback to persist overrides after every change. |
| shouldIgnore | (event: KeyboardEvent) => boolean | — | Filter to skip events. Defaults to ignoring inputs, textareas, and open modals. |
ShortcutRegistration
| Prop | Type | Default | Description |
|---|---|---|---|
| id required | string | — | Unique identifier for the shortcut action. |
| label required | string | — | Human-readable label shown in settings UI. |
| defaults | ShortcutDefinition[] | — | Default shortcut(s). Strings like "CmdOrCtrl+S" or per-platform objects. |
| handler required | (event) => void | boolean | — | Called when the shortcut fires. Return false to allow propagation. |
| when | () => boolean | — | Guard — shortcut only fires when this returns true. |
| section | string | "General" | Group in settings UI. |
| subsection | string | "General" | Subgroup in settings UI. |
| priority | number | 0 | Higher priority shortcuts are checked first. |
| allowDefault | boolean | false | If true, does not call preventDefault(). |
ShortcutManager Methods
| Prop | Type | Default | Description |
|---|---|---|---|
| register(reg) | ShortcutRegistration | — | Register a keyboard shortcut with id, label, defaults, and handler. |
| registerAll(regs) | ShortcutRegistration[] | — | Register multiple shortcuts at once. |
| unregister(id) | string | — | Remove a shortcut registration by id. |
| unregisterByPrefix(prefix) | string | — | Remove all registrations whose id starts with the given prefix. |
| has(id) | string → boolean | — | Check if a shortcut id is registered. |
| get(id) | string → ShortcutRegistration | undefined | — | Get a registration by id. |
| getShortcutsForAction(id) | string → string[] | — | Get the active shortcut strings for an action (respects overrides). |
| getDisplayShortcut(id) | string → string | null | — | Get the formatted display string for the primary shortcut. |
| getDisplayShortcuts(id) | string → string[] | — | Get formatted display strings for all shortcuts of an action. |
| setOverride(id, shortcuts) | async | — | Set user-customized shortcut(s) for an action. Validates conflicts. |
| resetToDefault(id) | async | — | Remove the user override for an action, restoring defaults. |
| resetAll() | async | — | Clear all user overrides. |
| hasOverride(id) | string → boolean | — | Check if an action has a user override. |
| checkConflict(shortcut, excludeId?) | ShortcutConflict | null | — | Check if a shortcut string conflicts with existing registrations. |
| handleKeyDown(event) | KeyboardEvent → boolean | — | Process a keyboard event. Returns true if a shortcut matched. |
| getSections() | ShortcutSection[] | — | Get all registrations grouped by section/subsection for settings UI. |
| init() | async | — | Load persisted overrides. Call once after creation. |
| destroy() | void | — | Clear all registrations and caches. |
Usage tips
- Use
CmdOrCtrlas a modifier prefix — it resolves toCmdon Mac andCtrlelsewhere. - Call
init()after creation to load persisted overrides before registering shortcuts. - The default
shouldIgnoreskips events in inputs, textareas, contenteditable, and when a modal is open. - Use
whenguards for context-dependent shortcuts (e.g., only active when a panel is focused). - Up to 3 shortcut bindings per action are supported.
getSections()returns grouped registrations — useful for building a keyboard shortcuts settings panel.