@ngrithms/hotkeys 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/cheatsheet/package.json +4 -0
- package/fesm2022/ngrithms-hotkeys-cheatsheet.mjs +317 -0
- package/fesm2022/ngrithms-hotkeys-cheatsheet.mjs.map +1 -0
- package/fesm2022/ngrithms-hotkeys.mjs +781 -0
- package/fesm2022/ngrithms-hotkeys.mjs.map +1 -0
- package/package.json +53 -0
- package/types/ngrithms-hotkeys-cheatsheet.d.ts +66 -0
- package/types/ngrithms-hotkeys.d.ts +270 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngrithms-hotkeys.mjs","sources":["../../../projects/hotkeys/src/lib/tokens/config.token.ts","../../../projects/hotkeys/src/lib/providers/provide-hotkeys.ts","../../../projects/hotkeys/src/lib/internal/key-utils.ts","../../../projects/hotkeys/src/lib/services/hotkeys.service.ts","../../../projects/hotkeys/src/lib/directives/hotkey-scope.directive.ts","../../../projects/hotkeys/src/lib/directives/hotkey.directive.ts","../../../projects/hotkeys/src/public-api.ts","../../../projects/hotkeys/src/ngrithms-hotkeys.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport type { ResolvedHotkeyConfig } from '../types/hotkey-config';\n\n/** Resolved hotkey configuration. Always returns a fully-defaulted object. */\nexport const HOTKEY_CONFIG = new InjectionToken<ResolvedHotkeyConfig>('HOTKEY_CONFIG');\n\nexport const DEFAULT_HOTKEY_CONFIG: ResolvedHotkeyConfig = {\n cheatsheetHotkey: '?',\n allowInInputs: false,\n sequenceTimeoutMs: 1000,\n preventDefault: true,\n allowDuplicateBindings: false,\n allowReservedShortcuts: false,\n};\n","import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { DEFAULT_HOTKEY_CONFIG, HOTKEY_CONFIG } from '../tokens/config.token';\nimport type { HotkeyConfig } from '../types/hotkey-config';\n\n/**\n * Register `@ngrithms/hotkeys` for the application.\n *\n * ```ts\n * // app.config.ts\n * export const appConfig: ApplicationConfig = {\n * providers: [provideHotkeys({ sequenceTimeoutMs: 1000 })],\n * };\n * ```\n */\nexport function provideHotkeys(config: HotkeyConfig = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: HOTKEY_CONFIG,\n useValue: { ...DEFAULT_HOTKEY_CONFIG, ...config },\n },\n ]);\n}\n","/**\n * Internal helpers for parsing combo strings and matching `KeyboardEvent`s.\n *\n * Combo grammar:\n * <step> ::= <modifier>+ ... + <key>\n * <key> ::= a printable character or special name (escape, enter, arrowup, ...)\n * modifiers: mod | meta | ctrl | alt | shift\n * sequences: <step> ' ' <step> ' ' ...\n *\n * 'mod+s' → single step, mod (= meta on Mac, ctrl elsewhere) + s\n * 'shift+/' → single step, shift + /\n * 'g i' → two-step sequence: press g then i within sequenceTimeoutMs\n * 'mod+shift+k' → mod + shift + k\n *\n * Key matching uses `KeyboardEvent.key` after lowercasing. This is the simplest\n * approach and matches what users type on a US layout. Non-US layouts may need\n * to bind by physical position — that's a future enhancement; for now use\n * letter-based bindings (`mod+k`) which work consistently across layouts since\n * `key` reports the produced letter.\n */\n\nexport interface ComboStep {\n /** Lowercased key name. */\n key: string;\n /** Platform-resolving modifier: meta on Mac, ctrl elsewhere. */\n mod: boolean;\n /** Explicit meta (cmd). */\n meta: boolean;\n /** Explicit ctrl. */\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n}\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n esc: 'escape',\n return: 'enter',\n del: 'delete',\n ins: 'insert',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n down: 'arrowdown',\n left: 'arrowleft',\n right: 'arrowright',\n plus: '+',\n};\n\nlet _isMac: boolean | null = null;\nexport function isMacPlatform(): boolean {\n if (_isMac !== null) return _isMac;\n if (typeof navigator === 'undefined') return (_isMac = false);\n // `navigator.platform` is deprecated but still the most reliable signal\n // for cmd-vs-ctrl resolution; userAgentData.platform is the modern alt.\n const platform =\n (navigator as { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n return (_isMac = /mac|iphone|ipad|ipod/i.test(platform));\n}\n\n/** Reset platform cache. Test-only. */\nexport function _resetPlatformCacheForTests(): void {\n _isMac = null;\n}\n\n/** Parse a combo string into one or more sequence steps. */\nexport function parseHotkey(spec: string): ComboStep[] {\n const steps = spec\n .trim()\n .split(/\\s+/)\n .filter((s) => s.length > 0);\n if (steps.length === 0) {\n throw new Error(`[@ngrithms/hotkeys] empty combo string`);\n }\n return steps.map(parseStep);\n}\n\n/**\n * Parse a single combo string or an array of combo strings into an array of\n * step sequences (one entry per combo). The matcher tries each in order — a\n * binding fires if any of its combos match.\n */\nexport function parseHotkeyMulti(keys: string | readonly string[]): ComboStep[][] {\n const list = typeof keys === 'string' ? [keys] : [...keys];\n if (list.length === 0) {\n throw new Error(`[@ngrithms/hotkeys] keys array cannot be empty`);\n }\n return list.map(parseHotkey);\n}\n\nfunction parseStep(step: string): ComboStep {\n const lower = step.toLowerCase();\n const result: ComboStep = {\n key: '',\n mod: false,\n meta: false,\n ctrl: false,\n alt: false,\n shift: false,\n };\n\n // Split into modifier-part and key-part. The key is always the segment after\n // the LAST '+', except when the spec ends in '++' (literal '+' key) or is just '+'.\n let keyPart: string;\n let modifierPart: string;\n if (lower === '+') {\n keyPart = '+';\n modifierPart = '';\n } else if (lower.endsWith('++')) {\n keyPart = '+';\n modifierPart = lower.slice(0, -2);\n } else {\n const idx = lower.lastIndexOf('+');\n if (idx === -1) {\n keyPart = lower;\n modifierPart = '';\n } else {\n keyPart = lower.slice(idx + 1);\n modifierPart = lower.slice(0, idx);\n }\n }\n\n if (keyPart === '') {\n throw new Error(`[@ngrithms/hotkeys] no key specified in \"${step}\"`);\n }\n\n if (modifierPart !== '') {\n for (const p of modifierPart.split('+')) {\n if (p === '') continue;\n switch (p) {\n case 'mod':\n result.mod = true;\n break;\n case 'meta':\n case 'cmd':\n case 'command':\n case 'super':\n case 'win':\n result.meta = true;\n break;\n case 'ctrl':\n case 'control':\n result.ctrl = true;\n break;\n case 'alt':\n case 'option':\n case 'opt':\n result.alt = true;\n break;\n case 'shift':\n result.shift = true;\n break;\n default:\n throw new Error(`[@ngrithms/hotkeys] unknown modifier \"${p}\" in \"${step}\"`);\n }\n }\n }\n\n result.key = SPECIAL_KEY_ALIASES[keyPart] ?? keyPart;\n return result;\n}\n\n/** Compare a parsed step against an actual KeyboardEvent. */\nexport function stepMatchesEvent(step: ComboStep, e: KeyboardEvent, mac: boolean): boolean {\n const eventKey = e.key.toLowerCase();\n if (eventKey !== step.key) return false;\n\n // `mod` resolves to meta on Mac, ctrl elsewhere.\n const wantMeta = step.meta || (step.mod && mac);\n const wantCtrl = step.ctrl || (step.mod && !mac);\n\n if (wantMeta !== e.metaKey) return false;\n if (wantCtrl !== e.ctrlKey) return false;\n if (step.alt !== e.altKey) return false;\n if (step.shift !== e.shiftKey) return false;\n\n return true;\n}\n\n/** Render a combo step into a display string (used by the cheatsheet). */\nexport function comboToDisplay(combo: string, mac = isMacPlatform()): string {\n const steps = parseHotkey(combo);\n return steps\n .map((s) => {\n const parts: string[] = [];\n if (s.mod || s.meta) parts.push(mac ? '⌘' : 'Ctrl');\n if (s.ctrl && !s.mod) parts.push('Ctrl');\n if (s.alt) parts.push(mac ? '⌥' : 'Alt');\n if (s.shift) parts.push(mac ? '⇧' : 'Shift');\n parts.push(prettifyKey(s.key));\n return parts.join(mac ? '' : '+');\n })\n .join(' then ');\n}\n\nfunction prettifyKey(key: string): string {\n switch (key) {\n case ' ':\n return 'Space';\n case 'arrowup':\n return '↑';\n case 'arrowdown':\n return '↓';\n case 'arrowleft':\n return '←';\n case 'arrowright':\n return '→';\n case 'escape':\n return 'Esc';\n case 'enter':\n return '⏎';\n case 'backspace':\n return '⌫';\n case 'delete':\n return 'Del';\n case 'tab':\n return 'Tab';\n default:\n return key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1);\n }\n}\n\n/**\n * Combos reserved by browsers / operating systems that never reach the page,\n * regardless of `preventDefault()`. Used by the dev-mode warning so users learn\n * about the limitation at registration time rather than discovering it in\n * production. Source: empirical testing across Chrome / Safari / Firefox on\n * macOS, Windows, and Linux as of 2026.\n *\n * Returns `null` when the combo is fine, or a short human description when it\n * is reserved (used in the warning text).\n */\nexport function reservedComboReason(combo: ComboStep): string | null {\n // Multi-step bindings have at most one reserved step at the end; check each.\n // (Most reserved combos are single-key with a modifier.)\n const k = combo.key;\n const hasMod = combo.mod || combo.meta || combo.ctrl;\n\n if (hasMod && !combo.shift && !combo.alt) {\n // Single mod (Cmd or Ctrl) + key — the largest reserved set.\n switch (k) {\n case 'n':\n return 'new window';\n case 't':\n return 'new tab';\n case 'w':\n return 'close tab';\n case 'q':\n return 'quit application';\n case 'r':\n return 'reload page';\n case 'l':\n return 'focus address bar';\n case 'tab':\n return 'switch tabs';\n }\n }\n\n if (hasMod && combo.shift && !combo.alt) {\n switch (k) {\n case 'n':\n return 'open incognito / private window';\n case 't':\n return 'reopen closed tab';\n case 'w':\n return 'close all tabs';\n case 'tab':\n return 'switch tabs (reverse)';\n }\n }\n\n if (k === 'f11') return 'toggle fullscreen';\n if (k === 'f5') return 'reload page';\n\n // Cmd+M minimizes on macOS — only flag if explicit meta (not mod, since mod+m\n // on Linux/Windows is fine).\n if (combo.meta && !combo.ctrl && !combo.shift && !combo.alt && k === 'm') {\n return 'minimize window (macOS)';\n }\n\n return null;\n}\n\n/** True if focus is in an editable element. */\nexport function isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n const tag = target.tagName;\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;\n const el = target as HTMLElement;\n // `isContentEditable` is the canonical browser check, but jsdom does not\n // populate it for detached / freshly-attached elements. Fall back to the\n // attribute so tests and SSR-attached nodes behave the same as real browsers.\n if (el.isContentEditable) return true;\n const attr = el.getAttribute('contenteditable');\n return attr !== null && attr !== 'false';\n}\n","import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport {\n DestroyRef,\n Injectable,\n PLATFORM_ID,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport {\n ComboStep,\n isEditableTarget,\n isMacPlatform,\n parseHotkeyMulti,\n reservedComboReason,\n stepMatchesEvent,\n} from '../internal/key-utils';\nimport { HOTKEY_CONFIG } from '../tokens/config.token';\nimport type { HotkeyBinding, HotkeyTriggerEvent } from '../types/hotkey-config';\n\n/** Internal record. Public consumers see `HotkeyBinding` via `registered()`. */\ninterface StoredBinding {\n readonly id: number;\n readonly keys: HotkeyBinding['keys'];\n /** Normalized to an array regardless of whether the user passed a string. */\n readonly comboStrings: readonly string[];\n /** Parsed counterparts of `comboStrings`, parallel-indexed. */\n readonly combos: readonly ComboStep[][];\n readonly scope: string;\n readonly category?: string;\n readonly description?: string;\n readonly allowInInputs?: boolean;\n readonly preventDefault?: boolean;\n readonly handler: (event: KeyboardEvent) => void;\n}\n\n/** Internal match result — points at the specific combo that fired. */\ninterface BindingMatch {\n readonly binding: StoredBinding;\n readonly comboIndex: number;\n}\n\n/**\n * Registry and dispatcher for keyboard shortcuts.\n *\n * Bindings are registered either programmatically via `register()` or\n * declaratively via the `(ngrHotkey)` directive (recommended — the directive\n * auto-unregisters on destroy).\n *\n * A single binding can listen for multiple combos by passing `keys` as an\n * array (e.g. `['mod+s', 'mod+shift+s']`). The handler fires once when any of\n * them matches; the `HotkeyTriggerEvent.combo` reports which one actually fired.\n *\n * Matching is global at the `document` level. When multiple bindings share a\n * combo, scope precedence wins: bindings in the active (top-of-stack) scope\n * fire first; if none match there, `'global'` bindings fire.\n *\n * Sequences (e.g. `'g i'`) require pressing each step within\n * `config.sequenceTimeoutMs`. The buffer resets on any non-modifier key that\n * doesn't extend a known sequence prefix.\n *\n * SSR-safe: on the server platform, `register()` is a no-op and signals stay\n * empty. All DOM listeners are guarded by `isPlatformBrowser`.\n */\n@Injectable({ providedIn: 'root' })\nexport class HotkeysService {\n private readonly config = inject(HOTKEY_CONFIG);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly mac = this.isBrowser ? isMacPlatform() : false;\n\n private readonly _bindings = signal<readonly StoredBinding[]>([]);\n private readonly _scopeStack = signal<readonly string[]>(['global']);\n private readonly _lastTriggered = signal<HotkeyTriggerEvent | null>(null);\n\n private readonly trigger$ = new Subject<HotkeyTriggerEvent>();\n\n /** All currently-registered bindings (read-only view for cheatsheets). */\n readonly registered: Signal<readonly HotkeyBinding[]> = computed(() =>\n this._bindings().map((b) => publicShape(b)),\n );\n\n /** Top-of-stack scope. Bindings in this scope (or `'global'`) are active. */\n readonly activeScope: Signal<string> = computed(\n () => this._scopeStack().at(-1) ?? 'global',\n );\n\n readonly lastTriggered: Signal<HotkeyTriggerEvent | null> = this._lastTriggered.asReadonly();\n\n readonly onTrigger: Observable<HotkeyTriggerEvent> = this.trigger$.asObservable();\n\n private nextId = 1;\n private sequenceBuffer: ComboStep[] = [];\n private sequenceTimerId: ReturnType<typeof setTimeout> | null = null;\n private keydownListener: ((e: KeyboardEvent) => void) | null = null;\n private listenerAttached = false;\n\n constructor() {\n if (!this.isBrowser) return;\n inject(DestroyRef).onDestroy(() => this.detachListener());\n }\n\n /**\n * Register a binding. Returns an unsubscribe function that removes it.\n * Idiomatic use is via the `(ngrHotkey)` directive, which calls this for you\n * and wires the unsubscribe into the host's `DestroyRef`.\n */\n register(binding: HotkeyBinding): () => void {\n if (!this.isBrowser) return () => undefined;\n\n const comboStrings: readonly string[] =\n typeof binding.keys === 'string' ? [binding.keys] : [...binding.keys];\n const combos = parseHotkeyMulti(binding.keys);\n\n const scope = binding.scope ?? 'global';\n const stored: StoredBinding = {\n id: this.nextId++,\n keys: binding.keys,\n comboStrings,\n combos,\n scope,\n category: binding.category,\n description: binding.description,\n allowInInputs: binding.allowInInputs,\n preventDefault: binding.preventDefault,\n handler: binding.handler,\n };\n\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n this.maybeWarnConflict(stored);\n this.maybeWarnReserved(stored);\n }\n\n this._bindings.update((list) => [...list, stored]);\n this.ensureListener();\n\n return () => {\n this._bindings.update((list) => list.filter((b) => b.id !== stored.id));\n };\n }\n\n /**\n * Push a scope onto the activation stack. Returns a function that pops it.\n * Prefer the `[ngrHotkeyScope]` directive for component-bound scopes — it\n * auto-pops on destroy.\n */\n pushScope(name: string): () => void {\n if (!this.isBrowser) return () => undefined;\n this._scopeStack.update((s) => [...s, name]);\n return () => {\n this._scopeStack.update((s) => {\n // Remove the last occurrence of `name` so nested pushes of the same\n // scope don't accidentally pop a sibling.\n const idx = s.lastIndexOf(name);\n if (idx === -1) return s;\n return [...s.slice(0, idx), ...s.slice(idx + 1)];\n });\n };\n }\n\n private ensureListener(): void {\n if (this.listenerAttached || !this.isBrowser) return;\n this.keydownListener = (e: KeyboardEvent) => this.handleKeyDown(e);\n this.document.addEventListener('keydown', this.keydownListener);\n this.listenerAttached = true;\n }\n\n private detachListener(): void {\n if (!this.listenerAttached || this.keydownListener === null) return;\n this.document.removeEventListener('keydown', this.keydownListener);\n this.keydownListener = null;\n this.listenerAttached = false;\n this.clearSequenceTimer();\n }\n\n private handleKeyDown(e: KeyboardEvent): void {\n // Ignore pure modifier presses (Shift/Ctrl/Alt/Meta by themselves) — they're\n // never the \"key\" of a combo and would otherwise spam the sequence buffer.\n const key = e.key.toLowerCase();\n if (key === 'shift' || key === 'control' || key === 'alt' || key === 'meta') {\n return;\n }\n\n const editable = isEditableTarget(e.target);\n const activeScope = this.activeScope();\n const candidates = this._bindings();\n\n // Extend the sequence buffer with this keystroke and try to match.\n const newBuffer: ComboStep[] = [...this.sequenceBuffer, eventToComboStep(e)];\n\n // First pass: exact match against the new buffer (full sequence completed).\n const fullMatch = this.findBindingMatch(candidates, newBuffer, activeScope, editable);\n if (fullMatch !== null) {\n this.fire(fullMatch, e);\n this.resetSequence();\n return;\n }\n\n // Second pass: is any longer binding still in progress (newBuffer is a prefix)?\n if (this.hasPrefixMatch(candidates, newBuffer, activeScope, editable)) {\n this.sequenceBuffer = newBuffer;\n this.scheduleSequenceTimeout();\n return;\n }\n\n // Otherwise: maybe the *new* keystroke alone starts a fresh sequence.\n this.resetSequence();\n const freshBuffer: ComboStep[] = [eventToComboStep(e)];\n const freshMatch = this.findBindingMatch(candidates, freshBuffer, activeScope, editable);\n if (freshMatch !== null) {\n this.fire(freshMatch, e);\n return;\n }\n if (this.hasPrefixMatch(candidates, freshBuffer, activeScope, editable)) {\n this.sequenceBuffer = freshBuffer;\n this.scheduleSequenceTimeout();\n }\n }\n\n private findBindingMatch(\n bindings: readonly StoredBinding[],\n buffer: ComboStep[],\n activeScope: string,\n editable: boolean,\n ): BindingMatch | null {\n // Prefer the active scope over global when both match the same combo.\n const findIn = (scope: string): BindingMatch | null => {\n for (const b of bindings) {\n if (b.scope !== scope) continue;\n if (!this.respectsInputFilter(b, editable)) continue;\n const idx = exactComboIndex(b, buffer);\n if (idx !== -1) return { binding: b, comboIndex: idx };\n }\n return null;\n };\n\n const inActiveScope = findIn(activeScope);\n if (inActiveScope !== null) return inActiveScope;\n if (activeScope !== 'global') {\n const inGlobal = findIn('global');\n if (inGlobal !== null) return inGlobal;\n }\n return null;\n }\n\n private hasPrefixMatch(\n bindings: readonly StoredBinding[],\n buffer: ComboStep[],\n activeScope: string,\n editable: boolean,\n ): boolean {\n return bindings.some(\n (b) =>\n (b.scope === 'global' || b.scope === activeScope) &&\n this.respectsInputFilter(b, editable) &&\n b.combos.some((c) => c.length > buffer.length && bufferIsPrefixOf(buffer, c)),\n );\n }\n\n private respectsInputFilter(b: StoredBinding, editable: boolean): boolean {\n if (!editable) return true;\n const allow = b.allowInInputs ?? this.config.allowInInputs;\n return allow;\n }\n\n private fire(match: BindingMatch, e: KeyboardEvent): void {\n const { binding, comboIndex } = match;\n const preventDefault = binding.preventDefault ?? this.config.preventDefault;\n if (preventDefault) e.preventDefault();\n const event: HotkeyTriggerEvent = {\n combo: binding.comboStrings[comboIndex],\n scope: binding.scope,\n ts: Date.now(),\n };\n this._lastTriggered.set(event);\n this.trigger$.next(event);\n try {\n binding.handler(e);\n } catch (err) {\n // Don't let a handler error tear down the listener for everything else.\n console.error('[@ngrithms/hotkeys] handler threw', err);\n }\n }\n\n private scheduleSequenceTimeout(): void {\n this.clearSequenceTimer();\n this.sequenceTimerId = setTimeout(() => {\n this.resetSequence();\n }, this.config.sequenceTimeoutMs);\n }\n\n private clearSequenceTimer(): void {\n if (this.sequenceTimerId !== null) {\n clearTimeout(this.sequenceTimerId);\n this.sequenceTimerId = null;\n }\n }\n\n private resetSequence(): void {\n this.sequenceBuffer = [];\n this.clearSequenceTimer();\n }\n\n private maybeWarnConflict(stored: StoredBinding): void {\n if (this.config.allowDuplicateBindings) return;\n for (const existing of this._bindings()) {\n if (existing.scope !== stored.scope) continue;\n for (let i = 0; i < stored.combos.length; i++) {\n const newCombo = stored.combos[i];\n for (let j = 0; j < existing.combos.length; j++) {\n if (combosAreEqual(newCombo, existing.combos[j])) {\n const combo = stored.comboStrings[i];\n const existingLabel = existing.description ?? `(no description, id ${existing.id})`;\n const newLabel = stored.description ?? `(no description, id ${stored.id})`;\n console.warn(\n `[@ngrithms/hotkeys] Two bindings registered for \"${combo}\" in scope \"${stored.scope}\". ` +\n `The later registration will be shadowed.\\n` +\n ` • ${existingLabel}\\n` +\n ` • ${newLabel} ← new\\n` +\n `Set provideHotkeys({ allowDuplicateBindings: true }) to silence.`,\n );\n }\n }\n }\n }\n }\n\n private maybeWarnReserved(stored: StoredBinding): void {\n if (this.config.allowReservedShortcuts) return;\n for (let i = 0; i < stored.combos.length; i++) {\n const combo = stored.combos[i];\n // Single-step combos with a modifier are the only realistic OS-reserved ones.\n // Skip sequence shortcuts.\n if (combo.length !== 1) continue;\n const reason = reservedComboReason(combo[0]);\n if (reason !== null) {\n console.warn(\n `[@ngrithms/hotkeys] \"${stored.comboStrings[i]}\" is reserved by the browser / OS (${reason}) ` +\n `and cannot be intercepted from JavaScript. The binding will not fire. ` +\n `See https://github.com/aboudbadra/ngrithms-hotkeys#reserved-shortcuts`,\n );\n }\n }\n }\n}\n\nfunction publicShape(b: StoredBinding): HotkeyBinding {\n return {\n keys: b.keys,\n handler: b.handler,\n scope: b.scope,\n category: b.category,\n description: b.description,\n allowInInputs: b.allowInInputs,\n preventDefault: b.preventDefault,\n };\n}\n\nfunction eventToComboStep(e: KeyboardEvent): ComboStep {\n return {\n key: e.key.toLowerCase(),\n mod: false,\n meta: e.metaKey,\n ctrl: e.ctrlKey,\n alt: e.altKey,\n shift: e.shiftKey,\n };\n}\n\nfunction stepEquals(spec: ComboStep, actual: ComboStep): boolean {\n // `actual` always has `mod=false` (events carry explicit meta/ctrl). For a\n // spec written with `mod`, accept either modifier based on platform.\n if (spec.key !== actual.key) return false;\n const mac = isMacPlatform();\n const wantMeta = spec.meta || (spec.mod && mac);\n const wantCtrl = spec.ctrl || (spec.mod && !mac);\n if (wantMeta !== actual.meta) return false;\n if (wantCtrl !== actual.ctrl) return false;\n if (spec.alt !== actual.alt) return false;\n if (spec.shift !== actual.shift) return false;\n return true;\n}\n\nfunction exactComboIndex(b: StoredBinding, buffer: ComboStep[]): number {\n for (let i = 0; i < b.combos.length; i++) {\n const combo = b.combos[i];\n if (combo.length !== buffer.length) continue;\n if (combo.every((s, j) => stepEquals(s, buffer[j]))) return i;\n }\n return -1;\n}\n\nfunction bufferIsPrefixOf(buffer: ComboStep[], combo: ComboStep[]): boolean {\n if (buffer.length > combo.length) return false;\n return buffer.every((s, i) => stepEquals(s, combo[i]));\n}\n\nfunction combosAreEqual(a: ComboStep[], b: ComboStep[]): boolean {\n // Compare parsed combos directly (modifier-by-modifier, key, sequence length).\n // We do NOT collapse `mod` into ctrl/meta here — `mod+s` and `meta+s` may or\n // may not collide depending on platform, but they're written differently so\n // we treat them as distinct registrations. The runtime matcher handles the\n // platform resolution; the warning is about literal source-level collisions.\n if (a.length !== b.length) return false;\n return a.every((sa, i) => {\n const sb = b[i];\n return (\n sa.key === sb.key &&\n sa.mod === sb.mod &&\n sa.meta === sb.meta &&\n sa.ctrl === sb.ctrl &&\n sa.alt === sb.alt &&\n sa.shift === sb.shift\n );\n });\n}\n\ndeclare const ngDevMode: boolean | undefined;\n\n// Re-export the event matcher used elsewhere so the rest of the lib has one path.\nexport { stepMatchesEvent };\n","import { Directive, OnDestroy, OnInit, inject, input } from '@angular/core';\nimport { HotkeysService } from '../services/hotkeys.service';\n\n/**\n * Activates a hotkey scope while this directive's host element is mounted.\n *\n * Use this around a modal, dialog, drawer, or any region whose shortcuts should\n * shadow global bindings while it is open. The scope is auto-popped on destroy\n * — no manual lifecycle wiring needed.\n *\n * Child `(ngrHotkey)` bindings inherit this scope automatically via DI.\n *\n * ```html\n * <dialog *ngIf=\"open()\" ngrHotkeyScope=\"modal\">\n * <button (ngrHotkey)=\"close()\" hotkey=\"escape\">Close</button>\n * </dialog>\n * ```\n *\n * While the dialog is mounted, `escape` fires `close()` instead of any global\n * `escape` binding. When the dialog unmounts, global bindings take over again.\n */\n@Directive({\n selector: '[ngrHotkeyScope]',\n standalone: true,\n})\nexport class HotkeyScopeDirective implements OnInit, OnDestroy {\n private readonly hotkeys = inject(HotkeysService);\n\n /** Scope name to push while this element is mounted. */\n readonly ngrHotkeyScope = input.required<string>();\n\n private release: (() => void) | null = null;\n\n ngOnInit(): void {\n this.release = this.hotkeys.pushScope(this.ngrHotkeyScope());\n }\n\n ngOnDestroy(): void {\n this.release?.();\n this.release = null;\n }\n}\n","import {\n Directive,\n EventEmitter,\n OnDestroy,\n OnInit,\n Output,\n inject,\n input,\n} from '@angular/core';\nimport { HotkeysService } from '../services/hotkeys.service';\nimport { HotkeyScopeDirective } from './hotkey-scope.directive';\n\n/**\n * Declarative shortcut binding. Registers on init, unregisters on destroy.\n *\n * ```html\n * <button (ngrHotkey)=\"save()\" hotkey=\"mod+s\" hotkeyDescription=\"Save\">Save</button>\n * <div (ngrHotkey)=\"gotoInbox()\" hotkey=\"g i\" hotkeyDescription=\"Go to inbox\"></div>\n * <!-- multi-combo: one handler, two combos -->\n * <button (ngrHotkey)=\"save()\" [hotkey]=\"['mod+s', 'mod+shift+s']\" hotkeyDescription=\"Save\">Save</button>\n * ```\n *\n * The host element does not need to be focused — matching is `document`-level.\n * Use `[ngrHotkeyScope]` on an ancestor to scope the binding to a UI region.\n */\n@Directive({\n selector: '[ngrHotkey]',\n standalone: true,\n})\nexport class HotkeyDirective implements OnInit, OnDestroy {\n private readonly hotkeys = inject(HotkeysService);\n // Inherit scope from the nearest ancestor `[ngrHotkeyScope]`, if any. This walks\n // the element-injector tree, so any ancestor element with the scope directive\n // wins. Read at ngOnInit time so the parent input is guaranteed set.\n private readonly parentScope = inject(HotkeyScopeDirective, { optional: true });\n\n /**\n * Combo string (`'mod+s'`, `'g i'`, `'escape'`, ...) or an array of strings\n * mapping multiple combos to the same handler. Required.\n */\n readonly hotkey = input.required<string | readonly string[]>();\n /** Logical scope. Defaults to the surrounding `ngrHotkeyScope`, or `'global'`. */\n readonly hotkeyScope = input<string | undefined>(undefined);\n /**\n * Optional sub-grouping label for the cheatsheet. Bindings sharing a category\n * appear under a single subheader within their scope.\n */\n readonly hotkeyCategory = input<string | undefined>(undefined);\n /** Description shown in the cheatsheet. Bindings without a description are hidden. */\n readonly hotkeyDescription = input<string | undefined>(undefined);\n /** Allow firing when focus is inside an input/textarea/contenteditable. */\n readonly hotkeyAllowInInputs = input<boolean | undefined>(undefined);\n /** Override `preventDefault` for this binding. */\n readonly hotkeyPreventDefault = input<boolean | undefined>(undefined);\n\n /** Fired when the combo is matched. The DOM `KeyboardEvent` is the payload. */\n @Output() readonly ngrHotkey = new EventEmitter<KeyboardEvent>();\n\n private dispose: (() => void) | null = null;\n\n ngOnInit(): void {\n const scope =\n this.hotkeyScope() ?? this.parentScope?.ngrHotkeyScope() ?? 'global';\n this.dispose = this.hotkeys.register({\n keys: this.hotkey(),\n scope,\n category: this.hotkeyCategory(),\n description: this.hotkeyDescription(),\n allowInInputs: this.hotkeyAllowInInputs(),\n preventDefault: this.hotkeyPreventDefault(),\n handler: (e) => this.ngrHotkey.emit(e),\n });\n }\n\n ngOnDestroy(): void {\n this.dispose?.();\n this.dispose = null;\n }\n}\n","/*\n * Public API surface of @ngrithms/hotkeys\n */\n\n// Setup\nexport { provideHotkeys } from './lib/providers/provide-hotkeys';\n\n// Service\nexport { HotkeysService } from './lib/services/hotkeys.service';\n\n// Directives\nexport { HotkeyDirective } from './lib/directives/hotkey.directive';\nexport { HotkeyScopeDirective } from './lib/directives/hotkey-scope.directive';\n\n// Tokens — for advanced override\nexport { HOTKEY_CONFIG, DEFAULT_HOTKEY_CONFIG } from './lib/tokens/config.token';\n\n// Types\nexport type {\n HotkeyConfig,\n ResolvedHotkeyConfig,\n HotkeyBinding,\n HotkeyTriggerEvent,\n} from './lib/types/hotkey-config';\n\n// Display helpers (used by the cheatsheet entry point)\nexport { comboToDisplay, isMacPlatform } from './lib/internal/key-utils';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ngrithms/hotkeys",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Modern Angular keyboard-shortcut library — standalone, signals-first, provideHotkeys(), scopes, sequences, SSR-safe, zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"hotkey",
|
|
8
|
+
"hotkeys",
|
|
9
|
+
"keyboard-shortcut",
|
|
10
|
+
"shortcut",
|
|
11
|
+
"keybinding",
|
|
12
|
+
"cmd-k",
|
|
13
|
+
"command-palette",
|
|
14
|
+
"standalone",
|
|
15
|
+
"signals",
|
|
16
|
+
"ngrithms"
|
|
17
|
+
],
|
|
18
|
+
"author": "Aboud Badra <albadra.aboud@gmail.com>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://github.com/aboudbadra/ngrithms-hotkeys",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/aboudbadra/ngrithms-hotkeys.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/aboudbadra/ngrithms-hotkeys/issues"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@angular/common": ">=17.2.0 <22.0.0",
|
|
30
|
+
"@angular/core": ">=17.2.0 <22.0.0",
|
|
31
|
+
"rxjs": ">=7.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"tslib": "^2.3.0"
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"module": "fesm2022/ngrithms-hotkeys.mjs",
|
|
38
|
+
"typings": "types/ngrithms-hotkeys.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
"./package.json": {
|
|
41
|
+
"default": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./types/ngrithms-hotkeys.d.ts",
|
|
45
|
+
"default": "./fesm2022/ngrithms-hotkeys.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./cheatsheet": {
|
|
48
|
+
"types": "./types/ngrithms-hotkeys-cheatsheet.d.ts",
|
|
49
|
+
"default": "./fesm2022/ngrithms-hotkeys-cheatsheet.mjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"type": "module"
|
|
53
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
|
|
3
|
+
interface CheatsheetEntry {
|
|
4
|
+
/** First / primary combo, shown prominently. */
|
|
5
|
+
primary: string;
|
|
6
|
+
/** Display string for the primary combo. */
|
|
7
|
+
primaryDisplay: string;
|
|
8
|
+
/** Additional combos that fire the same handler — shown as muted "also" badges. */
|
|
9
|
+
aliases: ReadonlyArray<{
|
|
10
|
+
combo: string;
|
|
11
|
+
display: string;
|
|
12
|
+
}>;
|
|
13
|
+
scope: string;
|
|
14
|
+
category: string | null;
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
interface CheatsheetSubgroup {
|
|
18
|
+
/** Sub-grouping label within a scope. `null` = no category (rendered before categorized rows). */
|
|
19
|
+
category: string | null;
|
|
20
|
+
entries: CheatsheetEntry[];
|
|
21
|
+
}
|
|
22
|
+
interface CheatsheetGroup {
|
|
23
|
+
scope: string;
|
|
24
|
+
subgroups: CheatsheetSubgroup[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Drop-in cheatsheet overlay listing every registered hotkey that has a
|
|
28
|
+
* `description`. Toggle with the `cheatsheetHotkey` config combo (default `'?'`)
|
|
29
|
+
* or by setting `[open]` programmatically.
|
|
30
|
+
*
|
|
31
|
+
* ```html
|
|
32
|
+
* <ngr-hotkey-cheatsheet /> <!-- toggled by '?' -->
|
|
33
|
+
* <ngr-hotkey-cheatsheet [open]="show()" /> <!-- controlled -->
|
|
34
|
+
* <ngr-hotkey-cheatsheet [searchable]="true" /> <!-- adds a filter input -->
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Bindings with the same `category` (set via `hotkeyCategory="..."` on the
|
|
38
|
+
* directive, or `category` on the service) appear under a shared subheader
|
|
39
|
+
* within their scope.
|
|
40
|
+
*
|
|
41
|
+
* Themable via CSS custom properties — see the component styles for the
|
|
42
|
+
* available `--ngr-hotkey-*` vars.
|
|
43
|
+
*/
|
|
44
|
+
declare class HotkeyCheatsheetComponent {
|
|
45
|
+
private readonly hotkeys;
|
|
46
|
+
private readonly config;
|
|
47
|
+
/** Cheatsheet title. */
|
|
48
|
+
readonly title: _angular_core.InputSignal<string>;
|
|
49
|
+
/** Externally control visibility. When `undefined`, the component manages its own state. */
|
|
50
|
+
readonly open: _angular_core.InputSignal<boolean | undefined>;
|
|
51
|
+
/** Show a search input that filters bindings by description / combo. Default `false`. */
|
|
52
|
+
readonly searchable: _angular_core.InputSignal<boolean>;
|
|
53
|
+
protected readonly mac: boolean;
|
|
54
|
+
private readonly internalOpen;
|
|
55
|
+
protected readonly query: _angular_core.WritableSignal<string>;
|
|
56
|
+
protected readonly visible: _angular_core.Signal<boolean>;
|
|
57
|
+
protected readonly groups: _angular_core.Signal<CheatsheetGroup[]>;
|
|
58
|
+
protected readonly filteredGroups: _angular_core.Signal<CheatsheetGroup[]>;
|
|
59
|
+
protected onKey(e: KeyboardEvent): void;
|
|
60
|
+
protected onSearch(e: Event): void;
|
|
61
|
+
protected close(): void;
|
|
62
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HotkeyCheatsheetComponent, never>;
|
|
63
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<HotkeyCheatsheetComponent, "ngr-hotkey-cheatsheet", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { HotkeyCheatsheetComponent };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { EnvironmentProviders, Signal, OnInit, OnDestroy, EventEmitter, InjectionToken } from '@angular/core';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import * as _ngrithms_hotkeys from '@ngrithms/hotkeys';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Public configuration for `@ngrithms/hotkeys`.
|
|
8
|
+
*
|
|
9
|
+
* Consumed via `provideHotkeys({...})` at application bootstrap, available at
|
|
10
|
+
* runtime via the `HOTKEY_CONFIG` injection token (resolved against defaults).
|
|
11
|
+
*/
|
|
12
|
+
interface HotkeyConfig {
|
|
13
|
+
/**
|
|
14
|
+
* Combo that toggles the built-in cheatsheet (if `@ngrithms/hotkeys/cheatsheet`
|
|
15
|
+
* is rendered). Set to `null` to disable the auto-registered binding. Default `'?'`.
|
|
16
|
+
*/
|
|
17
|
+
cheatsheetHotkey?: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Global default for whether shortcuts fire when focus is inside an
|
|
20
|
+
* `<input>`, `<textarea>`, `<select>`, or `[contenteditable]` element.
|
|
21
|
+
* Default `false` — block in inputs by default; per-binding override
|
|
22
|
+
* via `hotkeyAllowInInputs="true"` (directive) or `allowInInputs: true`
|
|
23
|
+
* (service).
|
|
24
|
+
*/
|
|
25
|
+
allowInInputs?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum time (ms) between the first and last key of a sequence binding
|
|
28
|
+
* such as `'g i'`. After this window elapses, the sequence buffer resets.
|
|
29
|
+
* Default `1000`.
|
|
30
|
+
*/
|
|
31
|
+
sequenceTimeoutMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Global default for `event.preventDefault()` on a matched shortcut.
|
|
34
|
+
* Default `true`. Override per binding when needed.
|
|
35
|
+
*/
|
|
36
|
+
preventDefault?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Suppress the dev-mode warning when two bindings share the same combo + scope
|
|
39
|
+
* (a same-scope collision — one will silently shadow the other). Default
|
|
40
|
+
* `false`. Set `true` if you deliberately layer bindings and don't want the
|
|
41
|
+
* warning. Production builds never emit the warning regardless.
|
|
42
|
+
*/
|
|
43
|
+
allowDuplicateBindings?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Suppress the dev-mode warning when a binding's combo is reserved by the
|
|
46
|
+
* browser or OS (e.g. `mod+n`, `mod+t`) and cannot be intercepted by any JS.
|
|
47
|
+
* Default `false`. Production builds never emit the warning regardless.
|
|
48
|
+
*/
|
|
49
|
+
allowReservedShortcuts?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Resolved (defaults-merged) shape used internally. */
|
|
52
|
+
type ResolvedHotkeyConfig = Required<HotkeyConfig>;
|
|
53
|
+
/** A single registered shortcut. */
|
|
54
|
+
interface HotkeyBinding {
|
|
55
|
+
/**
|
|
56
|
+
* Combo string, or an array of combo strings that all map to this handler.
|
|
57
|
+
*
|
|
58
|
+
* A single combo joins modifiers and a key with `+`; sequences (multi-step
|
|
59
|
+
* shortcuts like `'g i'`) separate steps with a single space.
|
|
60
|
+
*
|
|
61
|
+
* Examples:
|
|
62
|
+
* - `'mod+s'` — single combo
|
|
63
|
+
* - `'g i'` — two-step sequence
|
|
64
|
+
* - `['mod+s', 'mod+shift+s']` — one handler, two combos
|
|
65
|
+
*
|
|
66
|
+
* The `mod` modifier resolves to `meta` on macOS and `ctrl` everywhere else.
|
|
67
|
+
* To pin to a specific platform, use `meta` or `ctrl` explicitly.
|
|
68
|
+
*/
|
|
69
|
+
keys: string | readonly string[];
|
|
70
|
+
/** Handler invoked when any of the binding's combos matches and it is active. */
|
|
71
|
+
handler: (event: KeyboardEvent) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Logical group for scoped activation. Bindings in `'global'` (default) always
|
|
74
|
+
* fire when their combo is pressed. Bindings in any other scope only fire when
|
|
75
|
+
* that scope is at the top of the scope stack — see `pushScope` and the
|
|
76
|
+
* `ngrHotkeyScope` directive.
|
|
77
|
+
*/
|
|
78
|
+
scope?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Optional sub-grouping label for the cheatsheet. Bindings with the same
|
|
81
|
+
* `category` appear under a shared subheader within their scope. Useful when
|
|
82
|
+
* you have a dozen global shortcuts and want to break them into "Navigation",
|
|
83
|
+
* "Editing", "View", etc.
|
|
84
|
+
*/
|
|
85
|
+
category?: string;
|
|
86
|
+
/** Human description for the cheatsheet. Bindings without a description are hidden. */
|
|
87
|
+
description?: string;
|
|
88
|
+
/** Allow firing when focus is inside an input/textarea/contenteditable. */
|
|
89
|
+
allowInInputs?: boolean;
|
|
90
|
+
/** Call `event.preventDefault()` on match. Inherits the global config when omitted. */
|
|
91
|
+
preventDefault?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** Event emitted via `HotkeysService.onTrigger` (and reflected in `lastTriggered()`). */
|
|
94
|
+
interface HotkeyTriggerEvent {
|
|
95
|
+
/**
|
|
96
|
+
* The specific combo string that matched. For a multi-combo binding, this is
|
|
97
|
+
* the one that actually fired — not necessarily the first one registered.
|
|
98
|
+
*/
|
|
99
|
+
combo: string;
|
|
100
|
+
/** Scope the matched binding was registered in. */
|
|
101
|
+
scope: string;
|
|
102
|
+
/** `Date.now()` when the binding fired. */
|
|
103
|
+
ts: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Register `@ngrithms/hotkeys` for the application.
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* // app.config.ts
|
|
111
|
+
* export const appConfig: ApplicationConfig = {
|
|
112
|
+
* providers: [provideHotkeys({ sequenceTimeoutMs: 1000 })],
|
|
113
|
+
* };
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function provideHotkeys(config?: HotkeyConfig): EnvironmentProviders;
|
|
117
|
+
|
|
118
|
+
declare function isMacPlatform(): boolean;
|
|
119
|
+
/** Render a combo step into a display string (used by the cheatsheet). */
|
|
120
|
+
declare function comboToDisplay(combo: string, mac?: boolean): string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Registry and dispatcher for keyboard shortcuts.
|
|
124
|
+
*
|
|
125
|
+
* Bindings are registered either programmatically via `register()` or
|
|
126
|
+
* declaratively via the `(ngrHotkey)` directive (recommended — the directive
|
|
127
|
+
* auto-unregisters on destroy).
|
|
128
|
+
*
|
|
129
|
+
* A single binding can listen for multiple combos by passing `keys` as an
|
|
130
|
+
* array (e.g. `['mod+s', 'mod+shift+s']`). The handler fires once when any of
|
|
131
|
+
* them matches; the `HotkeyTriggerEvent.combo` reports which one actually fired.
|
|
132
|
+
*
|
|
133
|
+
* Matching is global at the `document` level. When multiple bindings share a
|
|
134
|
+
* combo, scope precedence wins: bindings in the active (top-of-stack) scope
|
|
135
|
+
* fire first; if none match there, `'global'` bindings fire.
|
|
136
|
+
*
|
|
137
|
+
* Sequences (e.g. `'g i'`) require pressing each step within
|
|
138
|
+
* `config.sequenceTimeoutMs`. The buffer resets on any non-modifier key that
|
|
139
|
+
* doesn't extend a known sequence prefix.
|
|
140
|
+
*
|
|
141
|
+
* SSR-safe: on the server platform, `register()` is a no-op and signals stay
|
|
142
|
+
* empty. All DOM listeners are guarded by `isPlatformBrowser`.
|
|
143
|
+
*/
|
|
144
|
+
declare class HotkeysService {
|
|
145
|
+
private readonly config;
|
|
146
|
+
private readonly document;
|
|
147
|
+
private readonly isBrowser;
|
|
148
|
+
private readonly mac;
|
|
149
|
+
private readonly _bindings;
|
|
150
|
+
private readonly _scopeStack;
|
|
151
|
+
private readonly _lastTriggered;
|
|
152
|
+
private readonly trigger$;
|
|
153
|
+
/** All currently-registered bindings (read-only view for cheatsheets). */
|
|
154
|
+
readonly registered: Signal<readonly HotkeyBinding[]>;
|
|
155
|
+
/** Top-of-stack scope. Bindings in this scope (or `'global'`) are active. */
|
|
156
|
+
readonly activeScope: Signal<string>;
|
|
157
|
+
readonly lastTriggered: Signal<HotkeyTriggerEvent | null>;
|
|
158
|
+
readonly onTrigger: Observable<HotkeyTriggerEvent>;
|
|
159
|
+
private nextId;
|
|
160
|
+
private sequenceBuffer;
|
|
161
|
+
private sequenceTimerId;
|
|
162
|
+
private keydownListener;
|
|
163
|
+
private listenerAttached;
|
|
164
|
+
constructor();
|
|
165
|
+
/**
|
|
166
|
+
* Register a binding. Returns an unsubscribe function that removes it.
|
|
167
|
+
* Idiomatic use is via the `(ngrHotkey)` directive, which calls this for you
|
|
168
|
+
* and wires the unsubscribe into the host's `DestroyRef`.
|
|
169
|
+
*/
|
|
170
|
+
register(binding: HotkeyBinding): () => void;
|
|
171
|
+
/**
|
|
172
|
+
* Push a scope onto the activation stack. Returns a function that pops it.
|
|
173
|
+
* Prefer the `[ngrHotkeyScope]` directive for component-bound scopes — it
|
|
174
|
+
* auto-pops on destroy.
|
|
175
|
+
*/
|
|
176
|
+
pushScope(name: string): () => void;
|
|
177
|
+
private ensureListener;
|
|
178
|
+
private detachListener;
|
|
179
|
+
private handleKeyDown;
|
|
180
|
+
private findBindingMatch;
|
|
181
|
+
private hasPrefixMatch;
|
|
182
|
+
private respectsInputFilter;
|
|
183
|
+
private fire;
|
|
184
|
+
private scheduleSequenceTimeout;
|
|
185
|
+
private clearSequenceTimer;
|
|
186
|
+
private resetSequence;
|
|
187
|
+
private maybeWarnConflict;
|
|
188
|
+
private maybeWarnReserved;
|
|
189
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HotkeysService, never>;
|
|
190
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<HotkeysService>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Declarative shortcut binding. Registers on init, unregisters on destroy.
|
|
195
|
+
*
|
|
196
|
+
* ```html
|
|
197
|
+
* <button (ngrHotkey)="save()" hotkey="mod+s" hotkeyDescription="Save">Save</button>
|
|
198
|
+
* <div (ngrHotkey)="gotoInbox()" hotkey="g i" hotkeyDescription="Go to inbox"></div>
|
|
199
|
+
* <!-- multi-combo: one handler, two combos -->
|
|
200
|
+
* <button (ngrHotkey)="save()" [hotkey]="['mod+s', 'mod+shift+s']" hotkeyDescription="Save">Save</button>
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* The host element does not need to be focused — matching is `document`-level.
|
|
204
|
+
* Use `[ngrHotkeyScope]` on an ancestor to scope the binding to a UI region.
|
|
205
|
+
*/
|
|
206
|
+
declare class HotkeyDirective implements OnInit, OnDestroy {
|
|
207
|
+
private readonly hotkeys;
|
|
208
|
+
private readonly parentScope;
|
|
209
|
+
/**
|
|
210
|
+
* Combo string (`'mod+s'`, `'g i'`, `'escape'`, ...) or an array of strings
|
|
211
|
+
* mapping multiple combos to the same handler. Required.
|
|
212
|
+
*/
|
|
213
|
+
readonly hotkey: _angular_core.InputSignal<string | readonly string[]>;
|
|
214
|
+
/** Logical scope. Defaults to the surrounding `ngrHotkeyScope`, or `'global'`. */
|
|
215
|
+
readonly hotkeyScope: _angular_core.InputSignal<string | undefined>;
|
|
216
|
+
/**
|
|
217
|
+
* Optional sub-grouping label for the cheatsheet. Bindings sharing a category
|
|
218
|
+
* appear under a single subheader within their scope.
|
|
219
|
+
*/
|
|
220
|
+
readonly hotkeyCategory: _angular_core.InputSignal<string | undefined>;
|
|
221
|
+
/** Description shown in the cheatsheet. Bindings without a description are hidden. */
|
|
222
|
+
readonly hotkeyDescription: _angular_core.InputSignal<string | undefined>;
|
|
223
|
+
/** Allow firing when focus is inside an input/textarea/contenteditable. */
|
|
224
|
+
readonly hotkeyAllowInInputs: _angular_core.InputSignal<boolean | undefined>;
|
|
225
|
+
/** Override `preventDefault` for this binding. */
|
|
226
|
+
readonly hotkeyPreventDefault: _angular_core.InputSignal<boolean | undefined>;
|
|
227
|
+
/** Fired when the combo is matched. The DOM `KeyboardEvent` is the payload. */
|
|
228
|
+
readonly ngrHotkey: EventEmitter<KeyboardEvent>;
|
|
229
|
+
private dispose;
|
|
230
|
+
ngOnInit(): void;
|
|
231
|
+
ngOnDestroy(): void;
|
|
232
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HotkeyDirective, never>;
|
|
233
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HotkeyDirective, "[ngrHotkey]", never, { "hotkey": { "alias": "hotkey"; "required": true; "isSignal": true; }; "hotkeyScope": { "alias": "hotkeyScope"; "required": false; "isSignal": true; }; "hotkeyCategory": { "alias": "hotkeyCategory"; "required": false; "isSignal": true; }; "hotkeyDescription": { "alias": "hotkeyDescription"; "required": false; "isSignal": true; }; "hotkeyAllowInInputs": { "alias": "hotkeyAllowInInputs"; "required": false; "isSignal": true; }; "hotkeyPreventDefault": { "alias": "hotkeyPreventDefault"; "required": false; "isSignal": true; }; }, { "ngrHotkey": "ngrHotkey"; }, never, never, true, never>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Activates a hotkey scope while this directive's host element is mounted.
|
|
238
|
+
*
|
|
239
|
+
* Use this around a modal, dialog, drawer, or any region whose shortcuts should
|
|
240
|
+
* shadow global bindings while it is open. The scope is auto-popped on destroy
|
|
241
|
+
* — no manual lifecycle wiring needed.
|
|
242
|
+
*
|
|
243
|
+
* Child `(ngrHotkey)` bindings inherit this scope automatically via DI.
|
|
244
|
+
*
|
|
245
|
+
* ```html
|
|
246
|
+
* <dialog *ngIf="open()" ngrHotkeyScope="modal">
|
|
247
|
+
* <button (ngrHotkey)="close()" hotkey="escape">Close</button>
|
|
248
|
+
* </dialog>
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* While the dialog is mounted, `escape` fires `close()` instead of any global
|
|
252
|
+
* `escape` binding. When the dialog unmounts, global bindings take over again.
|
|
253
|
+
*/
|
|
254
|
+
declare class HotkeyScopeDirective implements OnInit, OnDestroy {
|
|
255
|
+
private readonly hotkeys;
|
|
256
|
+
/** Scope name to push while this element is mounted. */
|
|
257
|
+
readonly ngrHotkeyScope: _angular_core.InputSignal<string>;
|
|
258
|
+
private release;
|
|
259
|
+
ngOnInit(): void;
|
|
260
|
+
ngOnDestroy(): void;
|
|
261
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HotkeyScopeDirective, never>;
|
|
262
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HotkeyScopeDirective, "[ngrHotkeyScope]", never, { "ngrHotkeyScope": { "alias": "ngrHotkeyScope"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Resolved hotkey configuration. Always returns a fully-defaulted object. */
|
|
266
|
+
declare const HOTKEY_CONFIG: InjectionToken<Required<_ngrithms_hotkeys.HotkeyConfig>>;
|
|
267
|
+
declare const DEFAULT_HOTKEY_CONFIG: ResolvedHotkeyConfig;
|
|
268
|
+
|
|
269
|
+
export { DEFAULT_HOTKEY_CONFIG, HOTKEY_CONFIG, HotkeyDirective, HotkeyScopeDirective, HotkeysService, comboToDisplay, isMacPlatform, provideHotkeys };
|
|
270
|
+
export type { HotkeyBinding, HotkeyConfig, HotkeyTriggerEvent, ResolvedHotkeyConfig };
|