@pyreon/hotkeys 0.6.0 → 0.8.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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +113 -181
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +3 -9
- package/src/registry.ts +6 -2
- package/lib/types/index2.d.ts +0 -130
- package/lib/types/index2.d.ts.map +0 -1
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"b111ad13-1","name":"parse.ts"},{"uid":"b111ad13-3","name":"registry.ts"},{"uid":"b111ad13-5","name":"use-hotkey.ts"},{"uid":"b111ad13-7","name":"use-hotkey-scope.ts"},{"uid":"b111ad13-9","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"b111ad13-1":{"renderedLength":1941,"gzipLength":838,"brotliLength":0,"metaUid":"b111ad13-0"},"b111ad13-3":{"renderedLength":2918,"gzipLength":1080,"brotliLength":0,"metaUid":"b111ad13-2"},"b111ad13-5":{"renderedLength":496,"gzipLength":295,"brotliLength":0,"metaUid":"b111ad13-4"},"b111ad13-7":{"renderedLength":421,"gzipLength":264,"brotliLength":0,"metaUid":"b111ad13-6"},"b111ad13-9":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"b111ad13-8"}},"nodeMetas":{"b111ad13-0":{"id":"/src/parse.ts","moduleParts":{"index.js":"b111ad13-1"},"imported":[],"importedBy":[{"uid":"b111ad13-8"},{"uid":"b111ad13-2"}]},"b111ad13-2":{"id":"/src/registry.ts","moduleParts":{"index.js":"b111ad13-3"},"imported":[{"uid":"b111ad13-11"},{"uid":"b111ad13-0"}],"importedBy":[{"uid":"b111ad13-8"},{"uid":"b111ad13-4"},{"uid":"b111ad13-6"}]},"b111ad13-4":{"id":"/src/use-hotkey.ts","moduleParts":{"index.js":"b111ad13-5"},"imported":[{"uid":"b111ad13-10"},{"uid":"b111ad13-2"}],"importedBy":[{"uid":"b111ad13-8"}]},"b111ad13-6":{"id":"/src/use-hotkey-scope.ts","moduleParts":{"index.js":"b111ad13-7"},"imported":[{"uid":"b111ad13-10"},{"uid":"b111ad13-2"}],"importedBy":[{"uid":"b111ad13-8"}]},"b111ad13-8":{"id":"/src/index.ts","moduleParts":{"index.js":"b111ad13-9"},"imported":[{"uid":"b111ad13-4"},{"uid":"b111ad13-6"},{"uid":"b111ad13-2"},{"uid":"b111ad13-0"}],"importedBy":[],"isEntry":true},"b111ad13-10":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"b111ad13-4"},{"uid":"b111ad13-6"}]},"b111ad13-11":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"b111ad13-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -125,7 +125,7 @@ function registerHotkey(shortcut, handler, options) {
|
|
|
125
125
|
stopPropagation: options?.stopPropagation === true,
|
|
126
126
|
enableOnInputs: options?.enableOnInputs === true,
|
|
127
127
|
enabled: options?.enabled ?? true,
|
|
128
|
-
description: options
|
|
128
|
+
...options?.description != null ? { description: options.description } : {}
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
entries.push(entry);
|
|
@@ -168,7 +168,7 @@ function getRegisteredHotkeys() {
|
|
|
168
168
|
return entries.map((e) => ({
|
|
169
169
|
shortcut: e.shortcut,
|
|
170
170
|
scope: e.options.scope,
|
|
171
|
-
description: e.options.description
|
|
171
|
+
...e.options.description != null ? { description: e.options.description } : {}
|
|
172
172
|
}));
|
|
173
173
|
}
|
|
174
174
|
function _resetHotkeys() {
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/parse.ts","../src/registry.ts","../src/use-hotkey.ts","../src/use-hotkey-scope.ts"],"sourcesContent":["import type { KeyCombo } from './types'\n\n// ─── Key aliases ─────────────────────────────────────────────────────────────\n\nconst 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\n/**\n * Parse a shortcut string like 'ctrl+shift+s' into a KeyCombo.\n * Supports aliases (esc, del, space, etc.) and mod (ctrl on Windows/Linux, meta on Mac).\n */\nexport function parseShortcut(shortcut: string): KeyCombo {\n const parts = shortcut.toLowerCase().trim().split('+')\n const combo: KeyCombo = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n key: '',\n }\n\n for (const part of parts) {\n const p = part.trim()\n if (p === 'ctrl' || p === 'control') {\n combo.ctrl = true\n } else if (p === 'shift') {\n combo.shift = true\n } else if (p === 'alt') {\n combo.alt = true\n } else if (p === 'meta' || p === 'cmd' || p === 'command') {\n combo.meta = true\n } else if (p === 'mod') {\n // mod = meta on Mac, ctrl elsewhere\n if (isMac()) {\n combo.meta = true\n } else {\n combo.ctrl = true\n }\n } else {\n combo.key = KEY_ALIASES[p] ?? p\n }\n }\n\n return combo\n}\n\n/**\n * Check if a KeyboardEvent matches a KeyCombo.\n */\nexport function matchesCombo(event: KeyboardEvent, combo: KeyCombo): boolean {\n if (event.ctrlKey !== combo.ctrl) return false\n if (event.shiftKey !== combo.shift) return false\n if (event.altKey !== combo.alt) return false\n if (event.metaKey !== combo.meta) return false\n\n const eventKey = event.key.toLowerCase()\n return eventKey === combo.key\n}\n\n/**\n * Format a KeyCombo back to a human-readable string.\n */\nexport function formatCombo(combo: KeyCombo): string {\n const parts: string[] = []\n if (combo.ctrl) parts.push('Ctrl')\n if (combo.shift) parts.push('Shift')\n if (combo.alt) parts.push('Alt')\n if (combo.meta) parts.push(isMac() ? '⌘' : 'Meta')\n parts.push(\n combo.key.length === 1 ? combo.key.toUpperCase() : capitalize(combo.key),\n )\n return parts.join('+')\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction isMac(): boolean {\n if (typeof navigator === 'undefined') return false\n return /mac|iphone|ipad|ipod/i.test(navigator.userAgent)\n}\n","import type { Signal } from '@pyreon/reactivity'\nimport { signal } from '@pyreon/reactivity'\nimport { matchesCombo, parseShortcut } from './parse'\nimport type { HotkeyEntry, HotkeyOptions } from './types'\n\n// ─── State ───────────────────────────────────────────────────────────────────\n\nconst entries: HotkeyEntry[] = []\nconst activeScopes = signal<Set<string>>(new Set(['global']))\nlet listenerAttached = false\n\n// ─── Input detection ─────────────────────────────────────────────────────────\n\nconst INPUT_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT'])\n\nfunction isInputFocused(event: KeyboardEvent): boolean {\n const target = event.target as HTMLElement | null\n if (!target) return false\n if (INPUT_TAGS.has(target.tagName)) return true\n if (target.isContentEditable) return true\n return false\n}\n\n// ─── Global listener ─────────────────────────────────────────────────────────\n\nfunction attachListener(): void {\n if (listenerAttached) return\n if (typeof window === 'undefined') return\n listenerAttached = true\n\n window.addEventListener('keydown', (event) => {\n const scopes = activeScopes.peek()\n\n for (const entry of entries) {\n // Check scope\n if (!scopes.has(entry.options.scope)) continue\n\n // Check enabled\n const enabled =\n typeof entry.options.enabled === 'function'\n ? entry.options.enabled()\n : entry.options.enabled\n if (!enabled) continue\n\n // Check input focus\n if (!entry.options.enableOnInputs && isInputFocused(event)) continue\n\n // Check key match\n if (!matchesCombo(event, entry.combo)) continue\n\n // Match found\n if (entry.options.preventDefault) event.preventDefault()\n if (entry.options.stopPropagation) event.stopPropagation()\n entry.handler(event)\n }\n })\n}\n\n// ─── Registration ────────────────────────────────────────────────────────────\n\n/**\n * Register a keyboard shortcut. Returns an unregister function.\n *\n * @example\n * ```ts\n * const unregister = registerHotkey('ctrl+s', (e) => save(), { description: 'Save' })\n * // later: unregister()\n * ```\n */\nexport function registerHotkey(\n shortcut: string,\n handler: (event: KeyboardEvent) => void,\n options?: HotkeyOptions,\n): () => void {\n attachListener()\n\n const entry: HotkeyEntry = {\n shortcut,\n combo: parseShortcut(shortcut),\n handler,\n options: {\n scope: options?.scope ?? 'global',\n preventDefault: options?.preventDefault !== false,\n stopPropagation: options?.stopPropagation === true,\n enableOnInputs: options?.enableOnInputs === true,\n enabled: options?.enabled ?? true,\n description: options?.description,\n },\n }\n\n entries.push(entry)\n\n return () => {\n const idx = entries.indexOf(entry)\n if (idx !== -1) entries.splice(idx, 1)\n }\n}\n\n// ─── Scope management ────────────────────────────────────────────────────────\n\n/**\n * Activate a hotkey scope. 'global' is always active.\n */\nexport function enableScope(scope: string): void {\n const current = activeScopes.peek()\n if (current.has(scope)) return\n const next = new Set(current)\n next.add(scope)\n activeScopes.set(next)\n}\n\n/**\n * Deactivate a hotkey scope. Cannot deactivate 'global'.\n */\nexport function disableScope(scope: string): void {\n if (scope === 'global') return\n const current = activeScopes.peek()\n if (!current.has(scope)) return\n const next = new Set(current)\n next.delete(scope)\n activeScopes.set(next)\n}\n\n/**\n * Get the currently active scopes as a reactive signal.\n */\nexport function getActiveScopes(): Signal<Set<string>> {\n return activeScopes\n}\n\n/**\n * Get all registered hotkeys (for building help dialogs).\n */\nexport function getRegisteredHotkeys(): ReadonlyArray<{\n shortcut: string\n scope: string\n description?: string\n}> {\n return entries.map((e) => ({\n shortcut: e.shortcut,\n scope: e.options.scope,\n description: e.options.description,\n }))\n}\n\n// ─── Reset (for testing) ────────────────────────────────────────────────────\n\nexport function _resetHotkeys(): void {\n entries.length = 0\n activeScopes.set(new Set(['global']))\n}\n","import { onUnmount } from '@pyreon/core'\nimport { registerHotkey } from './registry'\nimport type { HotkeyOptions } from './types'\n\n/**\n * Register a keyboard shortcut scoped to a component's lifecycle.\n * Automatically unregisters when the component unmounts.\n *\n * @example\n * ```ts\n * function Editor() {\n * useHotkey('ctrl+s', () => save(), { description: 'Save document' })\n * useHotkey('ctrl+z', () => undo())\n * useHotkey('ctrl+shift+z', () => redo())\n * // ...\n * }\n * ```\n */\nexport function useHotkey(\n shortcut: string,\n handler: (event: KeyboardEvent) => void,\n options?: HotkeyOptions,\n): void {\n const unregister = registerHotkey(shortcut, handler, options)\n onUnmount(unregister)\n}\n","import { onUnmount } from '@pyreon/core'\nimport { disableScope, enableScope } from './registry'\n\n/**\n * Activate a hotkey scope for the lifetime of a component.\n * When the component unmounts, the scope is deactivated.\n *\n * @example\n * ```tsx\n * function Modal() {\n * useHotkeyScope('modal')\n * useHotkey('escape', () => closeModal(), { scope: 'modal' })\n * // ...\n * }\n * ```\n */\nexport function useHotkeyScope(scope: string): void {\n enableScope(scope)\n onUnmount(() => disableScope(scope))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/parse.ts","../src/registry.ts","../src/use-hotkey.ts","../src/use-hotkey-scope.ts"],"sourcesContent":["import type { KeyCombo } from './types'\n\n// ─── Key aliases ─────────────────────────────────────────────────────────────\n\nconst 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\n/**\n * Parse a shortcut string like 'ctrl+shift+s' into a KeyCombo.\n * Supports aliases (esc, del, space, etc.) and mod (ctrl on Windows/Linux, meta on Mac).\n */\nexport function parseShortcut(shortcut: string): KeyCombo {\n const parts = shortcut.toLowerCase().trim().split('+')\n const combo: KeyCombo = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n key: '',\n }\n\n for (const part of parts) {\n const p = part.trim()\n if (p === 'ctrl' || p === 'control') {\n combo.ctrl = true\n } else if (p === 'shift') {\n combo.shift = true\n } else if (p === 'alt') {\n combo.alt = true\n } else if (p === 'meta' || p === 'cmd' || p === 'command') {\n combo.meta = true\n } else if (p === 'mod') {\n // mod = meta on Mac, ctrl elsewhere\n if (isMac()) {\n combo.meta = true\n } else {\n combo.ctrl = true\n }\n } else {\n combo.key = KEY_ALIASES[p] ?? p\n }\n }\n\n return combo\n}\n\n/**\n * Check if a KeyboardEvent matches a KeyCombo.\n */\nexport function matchesCombo(event: KeyboardEvent, combo: KeyCombo): boolean {\n if (event.ctrlKey !== combo.ctrl) return false\n if (event.shiftKey !== combo.shift) return false\n if (event.altKey !== combo.alt) return false\n if (event.metaKey !== combo.meta) return false\n\n const eventKey = event.key.toLowerCase()\n return eventKey === combo.key\n}\n\n/**\n * Format a KeyCombo back to a human-readable string.\n */\nexport function formatCombo(combo: KeyCombo): string {\n const parts: string[] = []\n if (combo.ctrl) parts.push('Ctrl')\n if (combo.shift) parts.push('Shift')\n if (combo.alt) parts.push('Alt')\n if (combo.meta) parts.push(isMac() ? '⌘' : 'Meta')\n parts.push(\n combo.key.length === 1 ? combo.key.toUpperCase() : capitalize(combo.key),\n )\n return parts.join('+')\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction isMac(): boolean {\n if (typeof navigator === 'undefined') return false\n return /mac|iphone|ipad|ipod/i.test(navigator.userAgent)\n}\n","import type { Signal } from '@pyreon/reactivity'\nimport { signal } from '@pyreon/reactivity'\nimport { matchesCombo, parseShortcut } from './parse'\nimport type { HotkeyEntry, HotkeyOptions } from './types'\n\n// ─── State ───────────────────────────────────────────────────────────────────\n\nconst entries: HotkeyEntry[] = []\nconst activeScopes = signal<Set<string>>(new Set(['global']))\nlet listenerAttached = false\n\n// ─── Input detection ─────────────────────────────────────────────────────────\n\nconst INPUT_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT'])\n\nfunction isInputFocused(event: KeyboardEvent): boolean {\n const target = event.target as HTMLElement | null\n if (!target) return false\n if (INPUT_TAGS.has(target.tagName)) return true\n if (target.isContentEditable) return true\n return false\n}\n\n// ─── Global listener ─────────────────────────────────────────────────────────\n\nfunction attachListener(): void {\n if (listenerAttached) return\n if (typeof window === 'undefined') return\n listenerAttached = true\n\n window.addEventListener('keydown', (event) => {\n const scopes = activeScopes.peek()\n\n for (const entry of entries) {\n // Check scope\n if (!scopes.has(entry.options.scope)) continue\n\n // Check enabled\n const enabled =\n typeof entry.options.enabled === 'function'\n ? entry.options.enabled()\n : entry.options.enabled\n if (!enabled) continue\n\n // Check input focus\n if (!entry.options.enableOnInputs && isInputFocused(event)) continue\n\n // Check key match\n if (!matchesCombo(event, entry.combo)) continue\n\n // Match found\n if (entry.options.preventDefault) event.preventDefault()\n if (entry.options.stopPropagation) event.stopPropagation()\n entry.handler(event)\n }\n })\n}\n\n// ─── Registration ────────────────────────────────────────────────────────────\n\n/**\n * Register a keyboard shortcut. Returns an unregister function.\n *\n * @example\n * ```ts\n * const unregister = registerHotkey('ctrl+s', (e) => save(), { description: 'Save' })\n * // later: unregister()\n * ```\n */\nexport function registerHotkey(\n shortcut: string,\n handler: (event: KeyboardEvent) => void,\n options?: HotkeyOptions,\n): () => void {\n attachListener()\n\n const entry: HotkeyEntry = {\n shortcut,\n combo: parseShortcut(shortcut),\n handler,\n options: {\n scope: options?.scope ?? 'global',\n preventDefault: options?.preventDefault !== false,\n stopPropagation: options?.stopPropagation === true,\n enableOnInputs: options?.enableOnInputs === true,\n enabled: options?.enabled ?? true,\n ...(options?.description != null\n ? { description: options.description }\n : {}),\n },\n }\n\n entries.push(entry)\n\n return () => {\n const idx = entries.indexOf(entry)\n if (idx !== -1) entries.splice(idx, 1)\n }\n}\n\n// ─── Scope management ────────────────────────────────────────────────────────\n\n/**\n * Activate a hotkey scope. 'global' is always active.\n */\nexport function enableScope(scope: string): void {\n const current = activeScopes.peek()\n if (current.has(scope)) return\n const next = new Set(current)\n next.add(scope)\n activeScopes.set(next)\n}\n\n/**\n * Deactivate a hotkey scope. Cannot deactivate 'global'.\n */\nexport function disableScope(scope: string): void {\n if (scope === 'global') return\n const current = activeScopes.peek()\n if (!current.has(scope)) return\n const next = new Set(current)\n next.delete(scope)\n activeScopes.set(next)\n}\n\n/**\n * Get the currently active scopes as a reactive signal.\n */\nexport function getActiveScopes(): Signal<Set<string>> {\n return activeScopes\n}\n\n/**\n * Get all registered hotkeys (for building help dialogs).\n */\nexport function getRegisteredHotkeys(): ReadonlyArray<{\n shortcut: string\n scope: string\n description?: string\n}> {\n return entries.map((e) => ({\n shortcut: e.shortcut,\n scope: e.options.scope,\n ...(e.options.description != null\n ? { description: e.options.description }\n : {}),\n }))\n}\n\n// ─── Reset (for testing) ────────────────────────────────────────────────────\n\nexport function _resetHotkeys(): void {\n entries.length = 0\n activeScopes.set(new Set(['global']))\n}\n","import { onUnmount } from '@pyreon/core'\nimport { registerHotkey } from './registry'\nimport type { HotkeyOptions } from './types'\n\n/**\n * Register a keyboard shortcut scoped to a component's lifecycle.\n * Automatically unregisters when the component unmounts.\n *\n * @example\n * ```ts\n * function Editor() {\n * useHotkey('ctrl+s', () => save(), { description: 'Save document' })\n * useHotkey('ctrl+z', () => undo())\n * useHotkey('ctrl+shift+z', () => redo())\n * // ...\n * }\n * ```\n */\nexport function useHotkey(\n shortcut: string,\n handler: (event: KeyboardEvent) => void,\n options?: HotkeyOptions,\n): void {\n const unregister = registerHotkey(shortcut, handler, options)\n onUnmount(unregister)\n}\n","import { onUnmount } from '@pyreon/core'\nimport { disableScope, enableScope } from './registry'\n\n/**\n * Activate a hotkey scope for the lifetime of a component.\n * When the component unmounts, the scope is deactivated.\n *\n * @example\n * ```tsx\n * function Modal() {\n * useHotkeyScope('modal')\n * useHotkey('escape', () => closeModal(), { scope: 'modal' })\n * // ...\n * }\n * ```\n */\nexport function useHotkeyScope(scope: string): void {\n enableScope(scope)\n onUnmount(() => disableScope(scope))\n}\n"],"mappings":"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"}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,198 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { signal } from "@pyreon/reactivity";
|
|
3
|
-
|
|
4
|
-
//#region src/parse.ts
|
|
1
|
+
import { Signal } from "@pyreon/reactivity";
|
|
5
2
|
|
|
3
|
+
//#region src/types.d.ts
|
|
6
4
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
meta: false,
|
|
17
|
-
key: ""
|
|
18
|
-
};
|
|
19
|
-
for (const part of parts) {
|
|
20
|
-
const p = part.trim();
|
|
21
|
-
if (p === "ctrl" || p === "control") combo.ctrl = true;else if (p === "shift") combo.shift = true;else if (p === "alt") combo.alt = true;else if (p === "meta" || p === "cmd" || p === "command") combo.meta = true;else if (p === "mod") {
|
|
22
|
-
if (isMac()) combo.meta = true;else combo.ctrl = true;
|
|
23
|
-
} else combo.key = KEY_ALIASES[p] ?? p;
|
|
24
|
-
}
|
|
25
|
-
return combo;
|
|
5
|
+
* A parsed key combination.
|
|
6
|
+
* Example: 'ctrl+shift+s' → { ctrl: true, shift: true, alt: false, meta: false, key: 's' }
|
|
7
|
+
*/
|
|
8
|
+
interface KeyCombo {
|
|
9
|
+
ctrl: boolean;
|
|
10
|
+
shift: boolean;
|
|
11
|
+
alt: boolean;
|
|
12
|
+
meta: boolean;
|
|
13
|
+
key: string;
|
|
26
14
|
}
|
|
27
15
|
/**
|
|
28
|
-
*
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
* Options for registering a hotkey.
|
|
17
|
+
*/
|
|
18
|
+
interface HotkeyOptions {
|
|
19
|
+
/** Scope for the hotkey — only active when this scope is active. Default: 'global' */
|
|
20
|
+
scope?: string;
|
|
21
|
+
/** Whether to prevent default browser behavior — default: true */
|
|
22
|
+
preventDefault?: boolean;
|
|
23
|
+
/** Whether to stop event propagation — default: false */
|
|
24
|
+
stopPropagation?: boolean;
|
|
25
|
+
/** Whether the hotkey fires when an input/textarea/contenteditable is focused — default: false */
|
|
26
|
+
enableOnInputs?: boolean;
|
|
27
|
+
/** Description of what this hotkey does — useful for help dialogs */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Whether the hotkey is enabled — default: true */
|
|
30
|
+
enabled?: boolean | (() => boolean);
|
|
36
31
|
}
|
|
37
32
|
/**
|
|
38
|
-
*
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
function isMac() {
|
|
53
|
-
if (typeof navigator === "undefined") return false;
|
|
54
|
-
return /mac|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
33
|
+
* A registered hotkey entry.
|
|
34
|
+
*/
|
|
35
|
+
interface HotkeyEntry {
|
|
36
|
+
/** The original shortcut string (e.g. 'ctrl+s') */
|
|
37
|
+
shortcut: string;
|
|
38
|
+
/** Parsed key combination */
|
|
39
|
+
combo: KeyCombo;
|
|
40
|
+
/** The callback to invoke */
|
|
41
|
+
handler: (event: KeyboardEvent) => void;
|
|
42
|
+
/** Options */
|
|
43
|
+
options: Required<Pick<HotkeyOptions, 'scope' | 'preventDefault' | 'stopPropagation' | 'enableOnInputs' | 'enabled'>> & {
|
|
44
|
+
description?: string;
|
|
45
|
+
};
|
|
55
46
|
}
|
|
56
|
-
|
|
57
47
|
//#endregion
|
|
58
|
-
//#region src/
|
|
59
|
-
|
|
60
|
-
function isInputFocused(event) {
|
|
61
|
-
const target = event.target;
|
|
62
|
-
if (!target) return false;
|
|
63
|
-
if (INPUT_TAGS.has(target.tagName)) return true;
|
|
64
|
-
if (target.isContentEditable) return true;
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
function attachListener() {
|
|
68
|
-
if (listenerAttached) return;
|
|
69
|
-
if (typeof window === "undefined") return;
|
|
70
|
-
listenerAttached = true;
|
|
71
|
-
window.addEventListener("keydown", event => {
|
|
72
|
-
const scopes = activeScopes.peek();
|
|
73
|
-
for (const entry of entries) {
|
|
74
|
-
if (!scopes.has(entry.options.scope)) continue;
|
|
75
|
-
if (!(typeof entry.options.enabled === "function" ? entry.options.enabled() : entry.options.enabled)) continue;
|
|
76
|
-
if (!entry.options.enableOnInputs && isInputFocused(event)) continue;
|
|
77
|
-
if (!matchesCombo(event, entry.combo)) continue;
|
|
78
|
-
if (entry.options.preventDefault) event.preventDefault();
|
|
79
|
-
if (entry.options.stopPropagation) event.stopPropagation();
|
|
80
|
-
entry.handler(event);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
48
|
+
//#region src/use-hotkey.d.ts
|
|
84
49
|
/**
|
|
85
|
-
* Register a keyboard shortcut
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
preventDefault: options?.preventDefault !== false,
|
|
102
|
-
stopPropagation: options?.stopPropagation === true,
|
|
103
|
-
enableOnInputs: options?.enableOnInputs === true,
|
|
104
|
-
enabled: options?.enabled ?? true,
|
|
105
|
-
description: options?.description
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
entries.push(entry);
|
|
109
|
-
return () => {
|
|
110
|
-
const idx = entries.indexOf(entry);
|
|
111
|
-
if (idx !== -1) entries.splice(idx, 1);
|
|
112
|
-
};
|
|
113
|
-
}
|
|
50
|
+
* Register a keyboard shortcut scoped to a component's lifecycle.
|
|
51
|
+
* Automatically unregisters when the component unmounts.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* function Editor() {
|
|
56
|
+
* useHotkey('ctrl+s', () => save(), { description: 'Save document' })
|
|
57
|
+
* useHotkey('ctrl+z', () => undo())
|
|
58
|
+
* useHotkey('ctrl+shift+z', () => redo())
|
|
59
|
+
* // ...
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function useHotkey(shortcut: string, handler: (event: KeyboardEvent) => void, options?: HotkeyOptions): void;
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/use-hotkey-scope.d.ts
|
|
114
66
|
/**
|
|
115
|
-
* Activate a hotkey scope
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
67
|
+
* Activate a hotkey scope for the lifetime of a component.
|
|
68
|
+
* When the component unmounts, the scope is deactivated.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```tsx
|
|
72
|
+
* function Modal() {
|
|
73
|
+
* useHotkeyScope('modal')
|
|
74
|
+
* useHotkey('escape', () => closeModal(), { scope: 'modal' })
|
|
75
|
+
* // ...
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function useHotkeyScope(scope: string): void;
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/registry.d.ts
|
|
124
82
|
/**
|
|
125
|
-
*
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
83
|
+
* Register a keyboard shortcut. Returns an unregister function.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const unregister = registerHotkey('ctrl+s', (e) => save(), { description: 'Save' })
|
|
88
|
+
* // later: unregister()
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
declare function registerHotkey(shortcut: string, handler: (event: KeyboardEvent) => void, options?: HotkeyOptions): () => void;
|
|
135
92
|
/**
|
|
136
|
-
*
|
|
137
|
-
*/
|
|
138
|
-
function
|
|
139
|
-
return activeScopes;
|
|
140
|
-
}
|
|
93
|
+
* Activate a hotkey scope. 'global' is always active.
|
|
94
|
+
*/
|
|
95
|
+
declare function enableScope(scope: string): void;
|
|
141
96
|
/**
|
|
142
|
-
*
|
|
143
|
-
*/
|
|
144
|
-
function
|
|
145
|
-
return entries.map(e => ({
|
|
146
|
-
shortcut: e.shortcut,
|
|
147
|
-
scope: e.options.scope,
|
|
148
|
-
description: e.options.description
|
|
149
|
-
}));
|
|
150
|
-
}
|
|
151
|
-
function _resetHotkeys() {
|
|
152
|
-
entries.length = 0;
|
|
153
|
-
activeScopes.set(new Set(["global"]));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/use-hotkey.ts
|
|
97
|
+
* Deactivate a hotkey scope. Cannot deactivate 'global'.
|
|
98
|
+
*/
|
|
99
|
+
declare function disableScope(scope: string): void;
|
|
158
100
|
/**
|
|
159
|
-
*
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
*
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
*/
|
|
172
|
-
function useHotkey(shortcut, handler, options) {
|
|
173
|
-
onUnmount(registerHotkey(shortcut, handler, options));
|
|
174
|
-
}
|
|
175
|
-
|
|
101
|
+
* Get the currently active scopes as a reactive signal.
|
|
102
|
+
*/
|
|
103
|
+
declare function getActiveScopes(): Signal<Set<string>>;
|
|
104
|
+
/**
|
|
105
|
+
* Get all registered hotkeys (for building help dialogs).
|
|
106
|
+
*/
|
|
107
|
+
declare function getRegisteredHotkeys(): ReadonlyArray<{
|
|
108
|
+
shortcut: string;
|
|
109
|
+
scope: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
}>;
|
|
112
|
+
declare function _resetHotkeys(): void;
|
|
176
113
|
//#endregion
|
|
177
|
-
//#region src/
|
|
114
|
+
//#region src/parse.d.ts
|
|
178
115
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
*
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
function useHotkeyScope(scope) {
|
|
192
|
-
enableScope(scope);
|
|
193
|
-
onUnmount(() => disableScope(scope));
|
|
194
|
-
}
|
|
195
|
-
|
|
116
|
+
* Parse a shortcut string like 'ctrl+shift+s' into a KeyCombo.
|
|
117
|
+
* Supports aliases (esc, del, space, etc.) and mod (ctrl on Windows/Linux, meta on Mac).
|
|
118
|
+
*/
|
|
119
|
+
declare function parseShortcut(shortcut: string): KeyCombo;
|
|
120
|
+
/**
|
|
121
|
+
* Check if a KeyboardEvent matches a KeyCombo.
|
|
122
|
+
*/
|
|
123
|
+
declare function matchesCombo(event: KeyboardEvent, combo: KeyCombo): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Format a KeyCombo back to a human-readable string.
|
|
126
|
+
*/
|
|
127
|
+
declare function formatCombo(combo: KeyCombo): string;
|
|
196
128
|
//#endregion
|
|
197
|
-
export { _resetHotkeys, disableScope, enableScope, formatCombo, getActiveScopes, getRegisteredHotkeys, matchesCombo, parseShortcut, registerHotkey, useHotkey, useHotkeyScope };
|
|
198
|
-
//# sourceMappingURL=
|
|
129
|
+
export { type HotkeyEntry, type HotkeyOptions, type KeyCombo, _resetHotkeys, disableScope, enableScope, formatCombo, getActiveScopes, getRegisteredHotkeys, matchesCombo, parseShortcut, registerHotkey, useHotkey, useHotkeyScope };
|
|
130
|
+
//# sourceMappingURL=index2.d.ts.map
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"
|
|
1
|
+
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/use-hotkey.ts","../../../src/use-hotkey-scope.ts","../../../src/registry.ts","../../../src/parse.ts"],"mappings":";;;;;;AAMA;UAAiB,QAAA;EACf,IAAA;EACA,KAAA;EACA,GAAA;EACA,IAAA;EACA,GAAA;AAAA;;;;UAMe,aAAA;EAAa;EAE5B,KAAA;EAF4B;EAI5B,cAAA;EAAA;EAEA,eAAA;EAEA;EAAA,cAAA;EAIA;EAFA,WAAA;EAEO;EAAP,OAAA;AAAA;;;;UAMe,WAAA;EASb;EAPF,QAAA;EAMiB;EAJjB,KAAA,EAAO,QAAA;EAFP;EAIA,OAAA,GAAU,KAAA,EAAO,aAAA;EAFV;EAIP,OAAA,EAAS,QAAA,CACP,IAAA,CACE,aAAA;IAOE,WAAA;EAAA;AAAA;;;;;AA9CR;;;;;;;;;;;AAWA;iBCCgB,SAAA,CACd,QAAA,UACA,OAAA,GAAU,KAAA,EAAO,aAAA,WACjB,OAAA,GAAU,aAAA;;;;;;ADfZ;;;;;;;;;;iBEUgB,cAAA,CAAe,KAAA;;;;AFV/B;;;;;;;;iBG+DgB,cAAA,CACd,QAAA,UACA,OAAA,GAAU,KAAA,EAAO,aAAA,WACjB,OAAA,GAAU,aAAA;;;AHvDZ;iBGwFgB,WAAA,CAAY,KAAA;;;;iBAWZ,YAAA,CAAa,KAAA;;;;iBAYb,eAAA,CAAA,GAAmB,MAAA,CAAO,GAAA;;;AH7F1C;iBGoGgB,oBAAA,CAAA,GAAwB,aAAA;EACtC,QAAA;EACA,KAAA;EACA,WAAA;AAAA;AAAA,iBAac,aAAA,CAAA;;;;;AHjJhB;;iBIgBgB,aAAA,CAAc,QAAA,WAAmB,QAAA;;;;iBAsCjC,YAAA,CAAa,KAAA,EAAO,aAAA,EAAe,KAAA,EAAO,QAAA;;;;iBAa1C,WAAA,CAAY,KAAA,EAAO,QAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Reactive keyboard shortcut management for Pyreon — scope-aware, conflict detection",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,13 +40,7 @@
|
|
|
40
40
|
"typecheck": "tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@pyreon/core": ">=0.
|
|
44
|
-
"@pyreon/reactivity": ">=0.
|
|
45
|
-
},
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"@happy-dom/global-registrator": "^20.8.3",
|
|
48
|
-
"@pyreon/core": ">=0.5.0 <1.0.0",
|
|
49
|
-
"@pyreon/reactivity": ">=0.5.0 <1.0.0",
|
|
50
|
-
"@vitus-labs/tools-lint": "^1.11.0"
|
|
43
|
+
"@pyreon/core": ">=0.7.0 <0.8.0",
|
|
44
|
+
"@pyreon/reactivity": ">=0.7.0 <0.8.0"
|
|
51
45
|
}
|
|
52
46
|
}
|
package/src/registry.ts
CHANGED
|
@@ -84,7 +84,9 @@ export function registerHotkey(
|
|
|
84
84
|
stopPropagation: options?.stopPropagation === true,
|
|
85
85
|
enableOnInputs: options?.enableOnInputs === true,
|
|
86
86
|
enabled: options?.enabled ?? true,
|
|
87
|
-
|
|
87
|
+
...(options?.description != null
|
|
88
|
+
? { description: options.description }
|
|
89
|
+
: {}),
|
|
88
90
|
},
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -139,7 +141,9 @@ export function getRegisteredHotkeys(): ReadonlyArray<{
|
|
|
139
141
|
return entries.map((e) => ({
|
|
140
142
|
shortcut: e.shortcut,
|
|
141
143
|
scope: e.options.scope,
|
|
142
|
-
|
|
144
|
+
...(e.options.description != null
|
|
145
|
+
? { description: e.options.description }
|
|
146
|
+
: {}),
|
|
143
147
|
}))
|
|
144
148
|
}
|
|
145
149
|
|
package/lib/types/index2.d.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { Signal } from "@pyreon/reactivity";
|
|
2
|
-
|
|
3
|
-
//#region src/types.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* A parsed key combination.
|
|
6
|
-
* Example: 'ctrl+shift+s' → { ctrl: true, shift: true, alt: false, meta: false, key: 's' }
|
|
7
|
-
*/
|
|
8
|
-
interface KeyCombo {
|
|
9
|
-
ctrl: boolean;
|
|
10
|
-
shift: boolean;
|
|
11
|
-
alt: boolean;
|
|
12
|
-
meta: boolean;
|
|
13
|
-
key: string;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Options for registering a hotkey.
|
|
17
|
-
*/
|
|
18
|
-
interface HotkeyOptions {
|
|
19
|
-
/** Scope for the hotkey — only active when this scope is active. Default: 'global' */
|
|
20
|
-
scope?: string;
|
|
21
|
-
/** Whether to prevent default browser behavior — default: true */
|
|
22
|
-
preventDefault?: boolean;
|
|
23
|
-
/** Whether to stop event propagation — default: false */
|
|
24
|
-
stopPropagation?: boolean;
|
|
25
|
-
/** Whether the hotkey fires when an input/textarea/contenteditable is focused — default: false */
|
|
26
|
-
enableOnInputs?: boolean;
|
|
27
|
-
/** Description of what this hotkey does — useful for help dialogs */
|
|
28
|
-
description?: string;
|
|
29
|
-
/** Whether the hotkey is enabled — default: true */
|
|
30
|
-
enabled?: boolean | (() => boolean);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* A registered hotkey entry.
|
|
34
|
-
*/
|
|
35
|
-
interface HotkeyEntry {
|
|
36
|
-
/** The original shortcut string (e.g. 'ctrl+s') */
|
|
37
|
-
shortcut: string;
|
|
38
|
-
/** Parsed key combination */
|
|
39
|
-
combo: KeyCombo;
|
|
40
|
-
/** The callback to invoke */
|
|
41
|
-
handler: (event: KeyboardEvent) => void;
|
|
42
|
-
/** Options */
|
|
43
|
-
options: Required<Pick<HotkeyOptions, 'scope' | 'preventDefault' | 'stopPropagation' | 'enableOnInputs' | 'enabled'>> & {
|
|
44
|
-
description?: string;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/use-hotkey.d.ts
|
|
49
|
-
/**
|
|
50
|
-
* Register a keyboard shortcut scoped to a component's lifecycle.
|
|
51
|
-
* Automatically unregisters when the component unmounts.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```ts
|
|
55
|
-
* function Editor() {
|
|
56
|
-
* useHotkey('ctrl+s', () => save(), { description: 'Save document' })
|
|
57
|
-
* useHotkey('ctrl+z', () => undo())
|
|
58
|
-
* useHotkey('ctrl+shift+z', () => redo())
|
|
59
|
-
* // ...
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
declare function useHotkey(shortcut: string, handler: (event: KeyboardEvent) => void, options?: HotkeyOptions): void;
|
|
64
|
-
//#endregion
|
|
65
|
-
//#region src/use-hotkey-scope.d.ts
|
|
66
|
-
/**
|
|
67
|
-
* Activate a hotkey scope for the lifetime of a component.
|
|
68
|
-
* When the component unmounts, the scope is deactivated.
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```tsx
|
|
72
|
-
* function Modal() {
|
|
73
|
-
* useHotkeyScope('modal')
|
|
74
|
-
* useHotkey('escape', () => closeModal(), { scope: 'modal' })
|
|
75
|
-
* // ...
|
|
76
|
-
* }
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
declare function useHotkeyScope(scope: string): void;
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region src/registry.d.ts
|
|
82
|
-
/**
|
|
83
|
-
* Register a keyboard shortcut. Returns an unregister function.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* ```ts
|
|
87
|
-
* const unregister = registerHotkey('ctrl+s', (e) => save(), { description: 'Save' })
|
|
88
|
-
* // later: unregister()
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
declare function registerHotkey(shortcut: string, handler: (event: KeyboardEvent) => void, options?: HotkeyOptions): () => void;
|
|
92
|
-
/**
|
|
93
|
-
* Activate a hotkey scope. 'global' is always active.
|
|
94
|
-
*/
|
|
95
|
-
declare function enableScope(scope: string): void;
|
|
96
|
-
/**
|
|
97
|
-
* Deactivate a hotkey scope. Cannot deactivate 'global'.
|
|
98
|
-
*/
|
|
99
|
-
declare function disableScope(scope: string): void;
|
|
100
|
-
/**
|
|
101
|
-
* Get the currently active scopes as a reactive signal.
|
|
102
|
-
*/
|
|
103
|
-
declare function getActiveScopes(): Signal<Set<string>>;
|
|
104
|
-
/**
|
|
105
|
-
* Get all registered hotkeys (for building help dialogs).
|
|
106
|
-
*/
|
|
107
|
-
declare function getRegisteredHotkeys(): ReadonlyArray<{
|
|
108
|
-
shortcut: string;
|
|
109
|
-
scope: string;
|
|
110
|
-
description?: string;
|
|
111
|
-
}>;
|
|
112
|
-
declare function _resetHotkeys(): void;
|
|
113
|
-
//#endregion
|
|
114
|
-
//#region src/parse.d.ts
|
|
115
|
-
/**
|
|
116
|
-
* Parse a shortcut string like 'ctrl+shift+s' into a KeyCombo.
|
|
117
|
-
* Supports aliases (esc, del, space, etc.) and mod (ctrl on Windows/Linux, meta on Mac).
|
|
118
|
-
*/
|
|
119
|
-
declare function parseShortcut(shortcut: string): KeyCombo;
|
|
120
|
-
/**
|
|
121
|
-
* Check if a KeyboardEvent matches a KeyCombo.
|
|
122
|
-
*/
|
|
123
|
-
declare function matchesCombo(event: KeyboardEvent, combo: KeyCombo): boolean;
|
|
124
|
-
/**
|
|
125
|
-
* Format a KeyCombo back to a human-readable string.
|
|
126
|
-
*/
|
|
127
|
-
declare function formatCombo(combo: KeyCombo): string;
|
|
128
|
-
//#endregion
|
|
129
|
-
export { type HotkeyEntry, type HotkeyOptions, type KeyCombo, _resetHotkeys, disableScope, enableScope, formatCombo, getActiveScopes, getRegisteredHotkeys, matchesCombo, parseShortcut, registerHotkey, useHotkey, useHotkeyScope };
|
|
130
|
-
//# sourceMappingURL=index2.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/types.ts","../../src/use-hotkey.ts","../../src/use-hotkey-scope.ts","../../src/registry.ts","../../src/parse.ts"],"mappings":";;;;;;AAMA;UAAiB,QAAA;EACf,IAAA;EACA,KAAA;EACA,GAAA;EACA,IAAA;EACA,GAAA;AAAA;;;;UAMe,aAAA;EAAa;EAE5B,KAAA;EAF4B;EAI5B,cAAA;EAAA;EAEA,eAAA;EAEA;EAAA,cAAA;EAIA;EAFA,WAAA;EAEO;EAAP,OAAA;AAAA;;;;UAMe,WAAA;EASb;EAPF,QAAA;EAMiB;EAJjB,KAAA,EAAO,QAAA;EAFP;EAIA,OAAA,GAAU,KAAA,EAAO,aAAA;EAFV;EAIP,OAAA,EAAS,QAAA,CACP,IAAA,CACE,aAAA;IAOE,WAAA;EAAA;AAAA;;;;;AA9CR;;;;;;;;;;;AAWA;iBCCgB,SAAA,CACd,QAAA,UACA,OAAA,GAAU,KAAA,EAAO,aAAA,WACjB,OAAA,GAAU,aAAA;;;;;;ADfZ;;;;;;;;;;iBEUgB,cAAA,CAAe,KAAA;;;;AFV/B;;;;;;;;iBG+DgB,cAAA,CACd,QAAA,UACA,OAAA,GAAU,KAAA,EAAO,aAAA,WACjB,OAAA,GAAU,aAAA;;;AHvDZ;iBGsFgB,WAAA,CAAY,KAAA;;;;iBAWZ,YAAA,CAAa,KAAA;;;;iBAYb,eAAA,CAAA,GAAmB,MAAA,CAAO,GAAA;;;AH3F1C;iBGkGgB,oBAAA,CAAA,GAAwB,aAAA;EACtC,QAAA;EACA,KAAA;EACA,WAAA;AAAA;AAAA,iBAWc,aAAA,CAAA;;;;;AH7IhB;;iBIgBgB,aAAA,CAAc,QAAA,WAAmB,QAAA;;;;iBAsCjC,YAAA,CAAa,KAAA,EAAO,aAAA,EAAe,KAAA,EAAO,QAAA;;;;iBAa1C,WAAA,CAAY,KAAA,EAAO,QAAA"}
|