@tanstack/hotkeys 0.0.1 → 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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -45
  3. package/dist/constants.cjs +444 -0
  4. package/dist/constants.cjs.map +1 -0
  5. package/dist/constants.d.cts +226 -0
  6. package/dist/constants.d.ts +226 -0
  7. package/dist/constants.js +428 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/format.cjs +178 -0
  10. package/dist/format.cjs.map +1 -0
  11. package/dist/format.d.cts +110 -0
  12. package/dist/format.d.ts +110 -0
  13. package/dist/format.js +175 -0
  14. package/dist/format.js.map +1 -0
  15. package/dist/hotkey-manager.cjs +420 -0
  16. package/dist/hotkey-manager.cjs.map +1 -0
  17. package/dist/hotkey-manager.d.cts +207 -0
  18. package/dist/hotkey-manager.d.ts +207 -0
  19. package/dist/hotkey-manager.js +419 -0
  20. package/dist/hotkey-manager.js.map +1 -0
  21. package/dist/hotkey.d.cts +278 -0
  22. package/dist/hotkey.d.ts +278 -0
  23. package/dist/index.cjs +54 -0
  24. package/dist/index.d.cts +11 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/index.js +11 -0
  27. package/dist/key-state-tracker.cjs +197 -0
  28. package/dist/key-state-tracker.cjs.map +1 -0
  29. package/dist/key-state-tracker.d.cts +107 -0
  30. package/dist/key-state-tracker.d.ts +107 -0
  31. package/dist/key-state-tracker.js +196 -0
  32. package/dist/key-state-tracker.js.map +1 -0
  33. package/dist/match.cjs +143 -0
  34. package/dist/match.cjs.map +1 -0
  35. package/dist/match.d.cts +79 -0
  36. package/dist/match.d.ts +79 -0
  37. package/dist/match.js +141 -0
  38. package/dist/match.js.map +1 -0
  39. package/dist/parse.cjs +266 -0
  40. package/dist/parse.cjs.map +1 -0
  41. package/dist/parse.d.cts +169 -0
  42. package/dist/parse.d.ts +169 -0
  43. package/dist/parse.js +258 -0
  44. package/dist/parse.js.map +1 -0
  45. package/dist/recorder.cjs +177 -0
  46. package/dist/recorder.cjs.map +1 -0
  47. package/dist/recorder.d.cts +108 -0
  48. package/dist/recorder.d.ts +108 -0
  49. package/dist/recorder.js +177 -0
  50. package/dist/recorder.js.map +1 -0
  51. package/dist/sequence.cjs +242 -0
  52. package/dist/sequence.cjs.map +1 -0
  53. package/dist/sequence.d.cts +109 -0
  54. package/dist/sequence.d.ts +109 -0
  55. package/dist/sequence.js +240 -0
  56. package/dist/sequence.js.map +1 -0
  57. package/dist/validate.cjs +116 -0
  58. package/dist/validate.cjs.map +1 -0
  59. package/dist/validate.d.cts +56 -0
  60. package/dist/validate.d.ts +56 -0
  61. package/dist/validate.js +114 -0
  62. package/dist/validate.js.map +1 -0
  63. package/package.json +55 -7
  64. package/src/constants.ts +514 -0
  65. package/src/format.ts +261 -0
  66. package/src/hotkey-manager.ts +822 -0
  67. package/src/hotkey.ts +411 -0
  68. package/src/index.ts +10 -0
  69. package/src/key-state-tracker.ts +249 -0
  70. package/src/match.ts +222 -0
  71. package/src/parse.ts +368 -0
  72. package/src/recorder.ts +266 -0
  73. package/src/sequence.ts +391 -0
  74. package/src/validate.ts +171 -0
package/dist/match.cjs ADDED
@@ -0,0 +1,143 @@
1
+ const require_constants = require('./constants.cjs');
2
+ const require_parse = require('./parse.cjs');
3
+
4
+ //#region src/match.ts
5
+ /**
6
+ * Checks if a KeyboardEvent matches a hotkey.
7
+ *
8
+ * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`
9
+ * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters
10
+ * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively.
11
+ *
12
+ * @param event - The KeyboardEvent to check
13
+ * @param hotkey - The hotkey string or ParsedHotkey to match against
14
+ * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
15
+ * @returns True if the event matches the hotkey
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * document.addEventListener('keydown', (event) => {
20
+ * if (matchesKeyboardEvent(event, 'Mod+S')) {
21
+ * event.preventDefault()
22
+ * handleSave()
23
+ * }
24
+ * })
25
+ * ```
26
+ */
27
+ function matchesKeyboardEvent(event, hotkey, platform = require_constants.detectPlatform()) {
28
+ const parsed = typeof hotkey === "string" ? require_parse.parseHotkey(hotkey, platform) : hotkey;
29
+ if (event.ctrlKey !== parsed.ctrl) return false;
30
+ if (event.shiftKey !== parsed.shift) return false;
31
+ if (event.altKey !== parsed.alt) return false;
32
+ if (event.metaKey !== parsed.meta) return false;
33
+ const eventKey = require_constants.normalizeKeyName(event.key);
34
+ const hotkeyKey = parsed.key;
35
+ if (eventKey.length === 1 && hotkeyKey.length === 1) {
36
+ if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) return true;
37
+ if (event.code && event.code.startsWith("Key")) {
38
+ const codeLetter = event.code.slice(3);
39
+ if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) return codeLetter.toUpperCase() === hotkeyKey.toUpperCase();
40
+ }
41
+ if (event.code && event.code.startsWith("Digit")) {
42
+ const codeDigit = event.code.slice(5);
43
+ if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) return codeDigit === hotkeyKey;
44
+ }
45
+ return false;
46
+ }
47
+ return eventKey === hotkeyKey;
48
+ }
49
+ /**
50
+ * Creates a keyboard event handler that calls the callback when the hotkey matches.
51
+ *
52
+ * @param hotkey - The hotkey string or ParsedHotkey to match
53
+ * @param callback - The function to call when the hotkey matches
54
+ * @param options - Options for matching and handling
55
+ * @returns A function that can be used as an event handler
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {
60
+ * console.log(`${hotkey} was pressed`)
61
+ * handleSave()
62
+ * })
63
+ *
64
+ * document.addEventListener('keydown', handler)
65
+ * ```
66
+ */
67
+ function createHotkeyHandler(hotkey, callback, options = {}) {
68
+ const { preventDefault = true, stopPropagation = true, platform } = options;
69
+ const resolvedPlatform = platform ?? require_constants.detectPlatform();
70
+ const hotkeyString = typeof hotkey === "string" ? hotkey : formatParsedHotkey(hotkey);
71
+ const parsed = typeof hotkey === "string" ? require_parse.parseHotkey(hotkey, resolvedPlatform) : hotkey;
72
+ const context = {
73
+ hotkey: hotkeyString,
74
+ parsedHotkey: parsed
75
+ };
76
+ return (event) => {
77
+ if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {
78
+ if (preventDefault) event.preventDefault();
79
+ if (stopPropagation) event.stopPropagation();
80
+ callback(event, context);
81
+ }
82
+ };
83
+ }
84
+ /**
85
+ * Creates a handler that matches multiple hotkeys.
86
+ *
87
+ * @param handlers - A map of hotkey strings to their handlers
88
+ * @param options - Options for matching and handling
89
+ * @returns A function that can be used as an event handler
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const handler = createMultiHotkeyHandler({
94
+ * 'Mod+S': (event, { hotkey }) => handleSave(),
95
+ * 'Mod+Z': (event, { hotkey }) => handleUndo(),
96
+ * 'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),
97
+ * })
98
+ *
99
+ * document.addEventListener('keydown', handler)
100
+ * ```
101
+ */
102
+ function createMultiHotkeyHandler(handlers, options = {}) {
103
+ const { preventDefault = true, stopPropagation = true, platform } = options;
104
+ const resolvedPlatform = platform ?? require_constants.detectPlatform();
105
+ const parsedHandlers = Object.entries(handlers).filter((entry) => Boolean(entry[1])).map(([hotkey, handler]) => {
106
+ const parsed = require_parse.parseHotkey(hotkey, resolvedPlatform);
107
+ return {
108
+ parsed,
109
+ handler,
110
+ context: {
111
+ hotkey,
112
+ parsedHotkey: parsed
113
+ }
114
+ };
115
+ });
116
+ return (event) => {
117
+ for (const { parsed, handler, context } of parsedHandlers) if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {
118
+ if (preventDefault) event.preventDefault();
119
+ if (stopPropagation) event.stopPropagation();
120
+ handler(event, context);
121
+ return;
122
+ }
123
+ };
124
+ }
125
+ /**
126
+ * Formats a ParsedHotkey back to a hotkey string.
127
+ * Used internally to provide the hotkey string in callback context.
128
+ */
129
+ function formatParsedHotkey(parsed) {
130
+ const parts = [];
131
+ if (parsed.ctrl) parts.push("Control");
132
+ if (parsed.alt) parts.push("Alt");
133
+ if (parsed.shift) parts.push("Shift");
134
+ if (parsed.meta) parts.push("Meta");
135
+ parts.push(parsed.key);
136
+ return parts.join("+");
137
+ }
138
+
139
+ //#endregion
140
+ exports.createHotkeyHandler = createHotkeyHandler;
141
+ exports.createMultiHotkeyHandler = createMultiHotkeyHandler;
142
+ exports.matchesKeyboardEvent = matchesKeyboardEvent;
143
+ //# sourceMappingURL=match.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.cjs","names":["detectPlatform","parseHotkey","normalizeKeyName"],"sources":["../src/match.ts"],"sourcesContent":["import { detectPlatform, normalizeKeyName } from './constants'\nimport { parseHotkey } from './parse'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyCallbackContext,\n ParsedHotkey,\n} from './hotkey'\n\n/**\n * Checks if a KeyboardEvent matches a hotkey.\n *\n * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`\n * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters\n * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively.\n *\n * @param event - The KeyboardEvent to check\n * @param hotkey - The hotkey string or ParsedHotkey to match against\n * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)\n * @returns True if the event matches the hotkey\n *\n * @example\n * ```ts\n * document.addEventListener('keydown', (event) => {\n * if (matchesKeyboardEvent(event, 'Mod+S')) {\n * event.preventDefault()\n * handleSave()\n * }\n * })\n * ```\n */\nexport function matchesKeyboardEvent(\n event: KeyboardEvent,\n hotkey: Hotkey | ParsedHotkey,\n platform: 'mac' | 'windows' | 'linux' = detectPlatform(),\n): boolean {\n const parsed =\n typeof hotkey === 'string' ? parseHotkey(hotkey, platform) : hotkey\n\n // Check modifiers\n if (event.ctrlKey !== parsed.ctrl) {\n return false\n }\n if (event.shiftKey !== parsed.shift) {\n return false\n }\n if (event.altKey !== parsed.alt) {\n return false\n }\n if (event.metaKey !== parsed.meta) {\n return false\n }\n\n // Check key (case-insensitive for letters)\n const eventKey = normalizeKeyName(event.key)\n const hotkeyKey = parsed.key\n\n // For single letters, compare case-insensitively\n if (eventKey.length === 1 && hotkeyKey.length === 1) {\n // First try matching with event.key\n if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) {\n return true\n }\n\n // Fallback to event.code for letter keys when event.key doesn't match\n // This handles cases like Command+Option+T on macOS where event.key is '†' instead of 'T'\n // event.code format for letter keys is \"KeyA\", \"KeyB\", etc. (always uppercase in browsers)\n if (event.code && event.code.startsWith('Key')) {\n const codeLetter = event.code.slice(3) // Remove \"Key\" prefix\n if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) {\n return codeLetter.toUpperCase() === hotkeyKey.toUpperCase()\n }\n }\n\n // Fallback to event.code for digit keys when event.key doesn't match\n // This handles cases like Shift+4 where event.key is '$' instead of '4'\n // event.code format for digit keys is \"Digit0\", \"Digit1\", etc.\n if (event.code && event.code.startsWith('Digit')) {\n const codeDigit = event.code.slice(5) // Remove \"Digit\" prefix\n if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) {\n return codeDigit === hotkeyKey\n }\n }\n\n return false\n }\n\n // For special keys, compare exactly (after normalization)\n return eventKey === hotkeyKey\n}\n\n/**\n * Options for creating a hotkey handler.\n */\nexport interface CreateHotkeyHandlerOptions {\n /** Prevent the default browser action when the hotkey matches. Defaults to true */\n preventDefault?: boolean\n /** Stop event propagation when the hotkey matches. Defaults to true */\n stopPropagation?: boolean\n /** The target platform for resolving 'Mod' */\n platform?: 'mac' | 'windows' | 'linux'\n}\n\n/**\n * Creates a keyboard event handler that calls the callback when the hotkey matches.\n *\n * @param hotkey - The hotkey string or ParsedHotkey to match\n * @param callback - The function to call when the hotkey matches\n * @param options - Options for matching and handling\n * @returns A function that can be used as an event handler\n *\n * @example\n * ```ts\n * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {\n * console.log(`${hotkey} was pressed`)\n * handleSave()\n * })\n *\n * document.addEventListener('keydown', handler)\n * ```\n */\nexport function createHotkeyHandler(\n hotkey: Hotkey | ParsedHotkey,\n callback: HotkeyCallback,\n options: CreateHotkeyHandlerOptions = {},\n): (event: KeyboardEvent) => void {\n const { preventDefault = true, stopPropagation = true, platform } = options\n const resolvedPlatform = platform ?? detectPlatform()\n\n const hotkeyString: Hotkey =\n typeof hotkey === 'string' ? hotkey : formatParsedHotkey(hotkey)\n const parsed =\n typeof hotkey === 'string' ? parseHotkey(hotkey, resolvedPlatform) : hotkey\n\n const context: HotkeyCallbackContext = {\n hotkey: hotkeyString,\n parsedHotkey: parsed,\n }\n\n return (event: KeyboardEvent) => {\n if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {\n if (preventDefault) {\n event.preventDefault()\n }\n if (stopPropagation) {\n event.stopPropagation()\n }\n callback(event, context)\n }\n }\n}\n\ntype MultiHotkeyHandler = { [K in Hotkey]?: HotkeyCallback }\n\n/**\n * Creates a handler that matches multiple hotkeys.\n *\n * @param handlers - A map of hotkey strings to their handlers\n * @param options - Options for matching and handling\n * @returns A function that can be used as an event handler\n *\n * @example\n * ```ts\n * const handler = createMultiHotkeyHandler({\n * 'Mod+S': (event, { hotkey }) => handleSave(),\n * 'Mod+Z': (event, { hotkey }) => handleUndo(),\n * 'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),\n * })\n *\n * document.addEventListener('keydown', handler)\n * ```\n */\nexport function createMultiHotkeyHandler(\n handlers: MultiHotkeyHandler,\n options: CreateHotkeyHandlerOptions = {},\n): (event: KeyboardEvent) => void {\n const { preventDefault = true, stopPropagation = true, platform } = options\n const resolvedPlatform = platform ?? detectPlatform()\n\n // Pre-parse all hotkeys for efficiency\n const parsedHandlers = Object.entries(handlers)\n .filter((entry): entry is [string, HotkeyCallback] => Boolean(entry[1]))\n .map(([hotkey, handler]) => {\n const parsed = parseHotkey(hotkey as Hotkey, resolvedPlatform)\n const context: HotkeyCallbackContext = {\n hotkey: hotkey as Hotkey,\n parsedHotkey: parsed,\n }\n return { parsed, handler, context }\n })\n\n return (event: KeyboardEvent) => {\n for (const { parsed, handler, context } of parsedHandlers) {\n if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {\n if (preventDefault) {\n event.preventDefault()\n }\n if (stopPropagation) {\n event.stopPropagation()\n }\n handler(event, context)\n return // Only handle the first match\n }\n }\n }\n}\n\n/**\n * Formats a ParsedHotkey back to a hotkey string.\n * Used internally to provide the hotkey string in callback context.\n */\nfunction formatParsedHotkey(parsed: ParsedHotkey): Hotkey {\n const parts: Array<string> = []\n\n if (parsed.ctrl) parts.push('Control')\n if (parsed.alt) parts.push('Alt')\n if (parsed.shift) parts.push('Shift')\n if (parsed.meta) parts.push('Meta')\n parts.push(parsed.key)\n\n return parts.join('+') as Hotkey\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,qBACd,OACA,QACA,WAAwCA,kCAAgB,EAC/C;CACT,MAAM,SACJ,OAAO,WAAW,WAAWC,0BAAY,QAAQ,SAAS,GAAG;AAG/D,KAAI,MAAM,YAAY,OAAO,KAC3B,QAAO;AAET,KAAI,MAAM,aAAa,OAAO,MAC5B,QAAO;AAET,KAAI,MAAM,WAAW,OAAO,IAC1B,QAAO;AAET,KAAI,MAAM,YAAY,OAAO,KAC3B,QAAO;CAIT,MAAM,WAAWC,mCAAiB,MAAM,IAAI;CAC5C,MAAM,YAAY,OAAO;AAGzB,KAAI,SAAS,WAAW,KAAK,UAAU,WAAW,GAAG;AAEnD,MAAI,SAAS,aAAa,KAAK,UAAU,aAAa,CACpD,QAAO;AAMT,MAAI,MAAM,QAAQ,MAAM,KAAK,WAAW,MAAM,EAAE;GAC9C,MAAM,aAAa,MAAM,KAAK,MAAM,EAAE;AACtC,OAAI,WAAW,WAAW,KAAK,aAAa,KAAK,WAAW,CAC1D,QAAO,WAAW,aAAa,KAAK,UAAU,aAAa;;AAO/D,MAAI,MAAM,QAAQ,MAAM,KAAK,WAAW,QAAQ,EAAE;GAChD,MAAM,YAAY,MAAM,KAAK,MAAM,EAAE;AACrC,OAAI,UAAU,WAAW,KAAK,UAAU,KAAK,UAAU,CACrD,QAAO,cAAc;;AAIzB,SAAO;;AAIT,QAAO,aAAa;;;;;;;;;;;;;;;;;;;;AAiCtB,SAAgB,oBACd,QACA,UACA,UAAsC,EAAE,EACR;CAChC,MAAM,EAAE,iBAAiB,MAAM,kBAAkB,MAAM,aAAa;CACpE,MAAM,mBAAmB,YAAYF,kCAAgB;CAErD,MAAM,eACJ,OAAO,WAAW,WAAW,SAAS,mBAAmB,OAAO;CAClE,MAAM,SACJ,OAAO,WAAW,WAAWC,0BAAY,QAAQ,iBAAiB,GAAG;CAEvE,MAAM,UAAiC;EACrC,QAAQ;EACR,cAAc;EACf;AAED,SAAQ,UAAyB;AAC/B,MAAI,qBAAqB,OAAO,QAAQ,iBAAiB,EAAE;AACzD,OAAI,eACF,OAAM,gBAAgB;AAExB,OAAI,gBACF,OAAM,iBAAiB;AAEzB,YAAS,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;AAyB9B,SAAgB,yBACd,UACA,UAAsC,EAAE,EACR;CAChC,MAAM,EAAE,iBAAiB,MAAM,kBAAkB,MAAM,aAAa;CACpE,MAAM,mBAAmB,YAAYD,kCAAgB;CAGrD,MAAM,iBAAiB,OAAO,QAAQ,SAAS,CAC5C,QAAQ,UAA6C,QAAQ,MAAM,GAAG,CAAC,CACvE,KAAK,CAAC,QAAQ,aAAa;EAC1B,MAAM,SAASC,0BAAY,QAAkB,iBAAiB;AAK9D,SAAO;GAAE;GAAQ;GAAS,SAJa;IAC7B;IACR,cAAc;IACf;GACkC;GACnC;AAEJ,SAAQ,UAAyB;AAC/B,OAAK,MAAM,EAAE,QAAQ,SAAS,aAAa,eACzC,KAAI,qBAAqB,OAAO,QAAQ,iBAAiB,EAAE;AACzD,OAAI,eACF,OAAM,gBAAgB;AAExB,OAAI,gBACF,OAAM,iBAAiB;AAEzB,WAAQ,OAAO,QAAQ;AACvB;;;;;;;;AAUR,SAAS,mBAAmB,QAA8B;CACxD,MAAM,QAAuB,EAAE;AAE/B,KAAI,OAAO,KAAM,OAAM,KAAK,UAAU;AACtC,KAAI,OAAO,IAAK,OAAM,KAAK,MAAM;AACjC,KAAI,OAAO,MAAO,OAAM,KAAK,QAAQ;AACrC,KAAI,OAAO,KAAM,OAAM,KAAK,OAAO;AACnC,OAAM,KAAK,OAAO,IAAI;AAEtB,QAAO,MAAM,KAAK,IAAI"}
@@ -0,0 +1,79 @@
1
+ import { Hotkey, HotkeyCallback, ParsedHotkey } from "./hotkey.cjs";
2
+
3
+ //#region src/match.d.ts
4
+ /**
5
+ * Checks if a KeyboardEvent matches a hotkey.
6
+ *
7
+ * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`
8
+ * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters
9
+ * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively.
10
+ *
11
+ * @param event - The KeyboardEvent to check
12
+ * @param hotkey - The hotkey string or ParsedHotkey to match against
13
+ * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
14
+ * @returns True if the event matches the hotkey
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * document.addEventListener('keydown', (event) => {
19
+ * if (matchesKeyboardEvent(event, 'Mod+S')) {
20
+ * event.preventDefault()
21
+ * handleSave()
22
+ * }
23
+ * })
24
+ * ```
25
+ */
26
+ declare function matchesKeyboardEvent(event: KeyboardEvent, hotkey: Hotkey | ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): boolean;
27
+ /**
28
+ * Options for creating a hotkey handler.
29
+ */
30
+ interface CreateHotkeyHandlerOptions {
31
+ /** Prevent the default browser action when the hotkey matches. Defaults to true */
32
+ preventDefault?: boolean;
33
+ /** Stop event propagation when the hotkey matches. Defaults to true */
34
+ stopPropagation?: boolean;
35
+ /** The target platform for resolving 'Mod' */
36
+ platform?: 'mac' | 'windows' | 'linux';
37
+ }
38
+ /**
39
+ * Creates a keyboard event handler that calls the callback when the hotkey matches.
40
+ *
41
+ * @param hotkey - The hotkey string or ParsedHotkey to match
42
+ * @param callback - The function to call when the hotkey matches
43
+ * @param options - Options for matching and handling
44
+ * @returns A function that can be used as an event handler
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {
49
+ * console.log(`${hotkey} was pressed`)
50
+ * handleSave()
51
+ * })
52
+ *
53
+ * document.addEventListener('keydown', handler)
54
+ * ```
55
+ */
56
+ declare function createHotkeyHandler(hotkey: Hotkey | ParsedHotkey, callback: HotkeyCallback, options?: CreateHotkeyHandlerOptions): (event: KeyboardEvent) => void;
57
+ type MultiHotkeyHandler = { [K in Hotkey]?: HotkeyCallback };
58
+ /**
59
+ * Creates a handler that matches multiple hotkeys.
60
+ *
61
+ * @param handlers - A map of hotkey strings to their handlers
62
+ * @param options - Options for matching and handling
63
+ * @returns A function that can be used as an event handler
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const handler = createMultiHotkeyHandler({
68
+ * 'Mod+S': (event, { hotkey }) => handleSave(),
69
+ * 'Mod+Z': (event, { hotkey }) => handleUndo(),
70
+ * 'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),
71
+ * })
72
+ *
73
+ * document.addEventListener('keydown', handler)
74
+ * ```
75
+ */
76
+ declare function createMultiHotkeyHandler(handlers: MultiHotkeyHandler, options?: CreateHotkeyHandlerOptions): (event: KeyboardEvent) => void;
77
+ //#endregion
78
+ export { CreateHotkeyHandlerOptions, createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
79
+ //# sourceMappingURL=match.d.cts.map
@@ -0,0 +1,79 @@
1
+ import { Hotkey, HotkeyCallback, ParsedHotkey } from "./hotkey.js";
2
+
3
+ //#region src/match.d.ts
4
+ /**
5
+ * Checks if a KeyboardEvent matches a hotkey.
6
+ *
7
+ * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`
8
+ * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters
9
+ * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively.
10
+ *
11
+ * @param event - The KeyboardEvent to check
12
+ * @param hotkey - The hotkey string or ParsedHotkey to match against
13
+ * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
14
+ * @returns True if the event matches the hotkey
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * document.addEventListener('keydown', (event) => {
19
+ * if (matchesKeyboardEvent(event, 'Mod+S')) {
20
+ * event.preventDefault()
21
+ * handleSave()
22
+ * }
23
+ * })
24
+ * ```
25
+ */
26
+ declare function matchesKeyboardEvent(event: KeyboardEvent, hotkey: Hotkey | ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): boolean;
27
+ /**
28
+ * Options for creating a hotkey handler.
29
+ */
30
+ interface CreateHotkeyHandlerOptions {
31
+ /** Prevent the default browser action when the hotkey matches. Defaults to true */
32
+ preventDefault?: boolean;
33
+ /** Stop event propagation when the hotkey matches. Defaults to true */
34
+ stopPropagation?: boolean;
35
+ /** The target platform for resolving 'Mod' */
36
+ platform?: 'mac' | 'windows' | 'linux';
37
+ }
38
+ /**
39
+ * Creates a keyboard event handler that calls the callback when the hotkey matches.
40
+ *
41
+ * @param hotkey - The hotkey string or ParsedHotkey to match
42
+ * @param callback - The function to call when the hotkey matches
43
+ * @param options - Options for matching and handling
44
+ * @returns A function that can be used as an event handler
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {
49
+ * console.log(`${hotkey} was pressed`)
50
+ * handleSave()
51
+ * })
52
+ *
53
+ * document.addEventListener('keydown', handler)
54
+ * ```
55
+ */
56
+ declare function createHotkeyHandler(hotkey: Hotkey | ParsedHotkey, callback: HotkeyCallback, options?: CreateHotkeyHandlerOptions): (event: KeyboardEvent) => void;
57
+ type MultiHotkeyHandler = { [K in Hotkey]?: HotkeyCallback };
58
+ /**
59
+ * Creates a handler that matches multiple hotkeys.
60
+ *
61
+ * @param handlers - A map of hotkey strings to their handlers
62
+ * @param options - Options for matching and handling
63
+ * @returns A function that can be used as an event handler
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const handler = createMultiHotkeyHandler({
68
+ * 'Mod+S': (event, { hotkey }) => handleSave(),
69
+ * 'Mod+Z': (event, { hotkey }) => handleUndo(),
70
+ * 'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),
71
+ * })
72
+ *
73
+ * document.addEventListener('keydown', handler)
74
+ * ```
75
+ */
76
+ declare function createMultiHotkeyHandler(handlers: MultiHotkeyHandler, options?: CreateHotkeyHandlerOptions): (event: KeyboardEvent) => void;
77
+ //#endregion
78
+ export { CreateHotkeyHandlerOptions, createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
79
+ //# sourceMappingURL=match.d.ts.map
package/dist/match.js ADDED
@@ -0,0 +1,141 @@
1
+ import { detectPlatform, normalizeKeyName } from "./constants.js";
2
+ import { parseHotkey } from "./parse.js";
3
+
4
+ //#region src/match.ts
5
+ /**
6
+ * Checks if a KeyboardEvent matches a hotkey.
7
+ *
8
+ * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`
9
+ * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters
10
+ * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively.
11
+ *
12
+ * @param event - The KeyboardEvent to check
13
+ * @param hotkey - The hotkey string or ParsedHotkey to match against
14
+ * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
15
+ * @returns True if the event matches the hotkey
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * document.addEventListener('keydown', (event) => {
20
+ * if (matchesKeyboardEvent(event, 'Mod+S')) {
21
+ * event.preventDefault()
22
+ * handleSave()
23
+ * }
24
+ * })
25
+ * ```
26
+ */
27
+ function matchesKeyboardEvent(event, hotkey, platform = detectPlatform()) {
28
+ const parsed = typeof hotkey === "string" ? parseHotkey(hotkey, platform) : hotkey;
29
+ if (event.ctrlKey !== parsed.ctrl) return false;
30
+ if (event.shiftKey !== parsed.shift) return false;
31
+ if (event.altKey !== parsed.alt) return false;
32
+ if (event.metaKey !== parsed.meta) return false;
33
+ const eventKey = normalizeKeyName(event.key);
34
+ const hotkeyKey = parsed.key;
35
+ if (eventKey.length === 1 && hotkeyKey.length === 1) {
36
+ if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) return true;
37
+ if (event.code && event.code.startsWith("Key")) {
38
+ const codeLetter = event.code.slice(3);
39
+ if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) return codeLetter.toUpperCase() === hotkeyKey.toUpperCase();
40
+ }
41
+ if (event.code && event.code.startsWith("Digit")) {
42
+ const codeDigit = event.code.slice(5);
43
+ if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) return codeDigit === hotkeyKey;
44
+ }
45
+ return false;
46
+ }
47
+ return eventKey === hotkeyKey;
48
+ }
49
+ /**
50
+ * Creates a keyboard event handler that calls the callback when the hotkey matches.
51
+ *
52
+ * @param hotkey - The hotkey string or ParsedHotkey to match
53
+ * @param callback - The function to call when the hotkey matches
54
+ * @param options - Options for matching and handling
55
+ * @returns A function that can be used as an event handler
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {
60
+ * console.log(`${hotkey} was pressed`)
61
+ * handleSave()
62
+ * })
63
+ *
64
+ * document.addEventListener('keydown', handler)
65
+ * ```
66
+ */
67
+ function createHotkeyHandler(hotkey, callback, options = {}) {
68
+ const { preventDefault = true, stopPropagation = true, platform } = options;
69
+ const resolvedPlatform = platform ?? detectPlatform();
70
+ const hotkeyString = typeof hotkey === "string" ? hotkey : formatParsedHotkey(hotkey);
71
+ const parsed = typeof hotkey === "string" ? parseHotkey(hotkey, resolvedPlatform) : hotkey;
72
+ const context = {
73
+ hotkey: hotkeyString,
74
+ parsedHotkey: parsed
75
+ };
76
+ return (event) => {
77
+ if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {
78
+ if (preventDefault) event.preventDefault();
79
+ if (stopPropagation) event.stopPropagation();
80
+ callback(event, context);
81
+ }
82
+ };
83
+ }
84
+ /**
85
+ * Creates a handler that matches multiple hotkeys.
86
+ *
87
+ * @param handlers - A map of hotkey strings to their handlers
88
+ * @param options - Options for matching and handling
89
+ * @returns A function that can be used as an event handler
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const handler = createMultiHotkeyHandler({
94
+ * 'Mod+S': (event, { hotkey }) => handleSave(),
95
+ * 'Mod+Z': (event, { hotkey }) => handleUndo(),
96
+ * 'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),
97
+ * })
98
+ *
99
+ * document.addEventListener('keydown', handler)
100
+ * ```
101
+ */
102
+ function createMultiHotkeyHandler(handlers, options = {}) {
103
+ const { preventDefault = true, stopPropagation = true, platform } = options;
104
+ const resolvedPlatform = platform ?? detectPlatform();
105
+ const parsedHandlers = Object.entries(handlers).filter((entry) => Boolean(entry[1])).map(([hotkey, handler]) => {
106
+ const parsed = parseHotkey(hotkey, resolvedPlatform);
107
+ return {
108
+ parsed,
109
+ handler,
110
+ context: {
111
+ hotkey,
112
+ parsedHotkey: parsed
113
+ }
114
+ };
115
+ });
116
+ return (event) => {
117
+ for (const { parsed, handler, context } of parsedHandlers) if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {
118
+ if (preventDefault) event.preventDefault();
119
+ if (stopPropagation) event.stopPropagation();
120
+ handler(event, context);
121
+ return;
122
+ }
123
+ };
124
+ }
125
+ /**
126
+ * Formats a ParsedHotkey back to a hotkey string.
127
+ * Used internally to provide the hotkey string in callback context.
128
+ */
129
+ function formatParsedHotkey(parsed) {
130
+ const parts = [];
131
+ if (parsed.ctrl) parts.push("Control");
132
+ if (parsed.alt) parts.push("Alt");
133
+ if (parsed.shift) parts.push("Shift");
134
+ if (parsed.meta) parts.push("Meta");
135
+ parts.push(parsed.key);
136
+ return parts.join("+");
137
+ }
138
+
139
+ //#endregion
140
+ export { createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
141
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","names":[],"sources":["../src/match.ts"],"sourcesContent":["import { detectPlatform, normalizeKeyName } from './constants'\nimport { parseHotkey } from './parse'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyCallbackContext,\n ParsedHotkey,\n} from './hotkey'\n\n/**\n * Checks if a KeyboardEvent matches a hotkey.\n *\n * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`\n * for letter keys (A-Z) and digit keys (0-9) when `key` produces special characters\n * (e.g., macOS Option+letter or Shift+number). Letter keys are matched case-insensitively.\n *\n * @param event - The KeyboardEvent to check\n * @param hotkey - The hotkey string or ParsedHotkey to match against\n * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)\n * @returns True if the event matches the hotkey\n *\n * @example\n * ```ts\n * document.addEventListener('keydown', (event) => {\n * if (matchesKeyboardEvent(event, 'Mod+S')) {\n * event.preventDefault()\n * handleSave()\n * }\n * })\n * ```\n */\nexport function matchesKeyboardEvent(\n event: KeyboardEvent,\n hotkey: Hotkey | ParsedHotkey,\n platform: 'mac' | 'windows' | 'linux' = detectPlatform(),\n): boolean {\n const parsed =\n typeof hotkey === 'string' ? parseHotkey(hotkey, platform) : hotkey\n\n // Check modifiers\n if (event.ctrlKey !== parsed.ctrl) {\n return false\n }\n if (event.shiftKey !== parsed.shift) {\n return false\n }\n if (event.altKey !== parsed.alt) {\n return false\n }\n if (event.metaKey !== parsed.meta) {\n return false\n }\n\n // Check key (case-insensitive for letters)\n const eventKey = normalizeKeyName(event.key)\n const hotkeyKey = parsed.key\n\n // For single letters, compare case-insensitively\n if (eventKey.length === 1 && hotkeyKey.length === 1) {\n // First try matching with event.key\n if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) {\n return true\n }\n\n // Fallback to event.code for letter keys when event.key doesn't match\n // This handles cases like Command+Option+T on macOS where event.key is '†' instead of 'T'\n // event.code format for letter keys is \"KeyA\", \"KeyB\", etc. (always uppercase in browsers)\n if (event.code && event.code.startsWith('Key')) {\n const codeLetter = event.code.slice(3) // Remove \"Key\" prefix\n if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) {\n return codeLetter.toUpperCase() === hotkeyKey.toUpperCase()\n }\n }\n\n // Fallback to event.code for digit keys when event.key doesn't match\n // This handles cases like Shift+4 where event.key is '$' instead of '4'\n // event.code format for digit keys is \"Digit0\", \"Digit1\", etc.\n if (event.code && event.code.startsWith('Digit')) {\n const codeDigit = event.code.slice(5) // Remove \"Digit\" prefix\n if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) {\n return codeDigit === hotkeyKey\n }\n }\n\n return false\n }\n\n // For special keys, compare exactly (after normalization)\n return eventKey === hotkeyKey\n}\n\n/**\n * Options for creating a hotkey handler.\n */\nexport interface CreateHotkeyHandlerOptions {\n /** Prevent the default browser action when the hotkey matches. Defaults to true */\n preventDefault?: boolean\n /** Stop event propagation when the hotkey matches. Defaults to true */\n stopPropagation?: boolean\n /** The target platform for resolving 'Mod' */\n platform?: 'mac' | 'windows' | 'linux'\n}\n\n/**\n * Creates a keyboard event handler that calls the callback when the hotkey matches.\n *\n * @param hotkey - The hotkey string or ParsedHotkey to match\n * @param callback - The function to call when the hotkey matches\n * @param options - Options for matching and handling\n * @returns A function that can be used as an event handler\n *\n * @example\n * ```ts\n * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {\n * console.log(`${hotkey} was pressed`)\n * handleSave()\n * })\n *\n * document.addEventListener('keydown', handler)\n * ```\n */\nexport function createHotkeyHandler(\n hotkey: Hotkey | ParsedHotkey,\n callback: HotkeyCallback,\n options: CreateHotkeyHandlerOptions = {},\n): (event: KeyboardEvent) => void {\n const { preventDefault = true, stopPropagation = true, platform } = options\n const resolvedPlatform = platform ?? detectPlatform()\n\n const hotkeyString: Hotkey =\n typeof hotkey === 'string' ? hotkey : formatParsedHotkey(hotkey)\n const parsed =\n typeof hotkey === 'string' ? parseHotkey(hotkey, resolvedPlatform) : hotkey\n\n const context: HotkeyCallbackContext = {\n hotkey: hotkeyString,\n parsedHotkey: parsed,\n }\n\n return (event: KeyboardEvent) => {\n if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {\n if (preventDefault) {\n event.preventDefault()\n }\n if (stopPropagation) {\n event.stopPropagation()\n }\n callback(event, context)\n }\n }\n}\n\ntype MultiHotkeyHandler = { [K in Hotkey]?: HotkeyCallback }\n\n/**\n * Creates a handler that matches multiple hotkeys.\n *\n * @param handlers - A map of hotkey strings to their handlers\n * @param options - Options for matching and handling\n * @returns A function that can be used as an event handler\n *\n * @example\n * ```ts\n * const handler = createMultiHotkeyHandler({\n * 'Mod+S': (event, { hotkey }) => handleSave(),\n * 'Mod+Z': (event, { hotkey }) => handleUndo(),\n * 'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),\n * })\n *\n * document.addEventListener('keydown', handler)\n * ```\n */\nexport function createMultiHotkeyHandler(\n handlers: MultiHotkeyHandler,\n options: CreateHotkeyHandlerOptions = {},\n): (event: KeyboardEvent) => void {\n const { preventDefault = true, stopPropagation = true, platform } = options\n const resolvedPlatform = platform ?? detectPlatform()\n\n // Pre-parse all hotkeys for efficiency\n const parsedHandlers = Object.entries(handlers)\n .filter((entry): entry is [string, HotkeyCallback] => Boolean(entry[1]))\n .map(([hotkey, handler]) => {\n const parsed = parseHotkey(hotkey as Hotkey, resolvedPlatform)\n const context: HotkeyCallbackContext = {\n hotkey: hotkey as Hotkey,\n parsedHotkey: parsed,\n }\n return { parsed, handler, context }\n })\n\n return (event: KeyboardEvent) => {\n for (const { parsed, handler, context } of parsedHandlers) {\n if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {\n if (preventDefault) {\n event.preventDefault()\n }\n if (stopPropagation) {\n event.stopPropagation()\n }\n handler(event, context)\n return // Only handle the first match\n }\n }\n }\n}\n\n/**\n * Formats a ParsedHotkey back to a hotkey string.\n * Used internally to provide the hotkey string in callback context.\n */\nfunction formatParsedHotkey(parsed: ParsedHotkey): Hotkey {\n const parts: Array<string> = []\n\n if (parsed.ctrl) parts.push('Control')\n if (parsed.alt) parts.push('Alt')\n if (parsed.shift) parts.push('Shift')\n if (parsed.meta) parts.push('Meta')\n parts.push(parsed.key)\n\n return parts.join('+') as Hotkey\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,qBACd,OACA,QACA,WAAwC,gBAAgB,EAC/C;CACT,MAAM,SACJ,OAAO,WAAW,WAAW,YAAY,QAAQ,SAAS,GAAG;AAG/D,KAAI,MAAM,YAAY,OAAO,KAC3B,QAAO;AAET,KAAI,MAAM,aAAa,OAAO,MAC5B,QAAO;AAET,KAAI,MAAM,WAAW,OAAO,IAC1B,QAAO;AAET,KAAI,MAAM,YAAY,OAAO,KAC3B,QAAO;CAIT,MAAM,WAAW,iBAAiB,MAAM,IAAI;CAC5C,MAAM,YAAY,OAAO;AAGzB,KAAI,SAAS,WAAW,KAAK,UAAU,WAAW,GAAG;AAEnD,MAAI,SAAS,aAAa,KAAK,UAAU,aAAa,CACpD,QAAO;AAMT,MAAI,MAAM,QAAQ,MAAM,KAAK,WAAW,MAAM,EAAE;GAC9C,MAAM,aAAa,MAAM,KAAK,MAAM,EAAE;AACtC,OAAI,WAAW,WAAW,KAAK,aAAa,KAAK,WAAW,CAC1D,QAAO,WAAW,aAAa,KAAK,UAAU,aAAa;;AAO/D,MAAI,MAAM,QAAQ,MAAM,KAAK,WAAW,QAAQ,EAAE;GAChD,MAAM,YAAY,MAAM,KAAK,MAAM,EAAE;AACrC,OAAI,UAAU,WAAW,KAAK,UAAU,KAAK,UAAU,CACrD,QAAO,cAAc;;AAIzB,SAAO;;AAIT,QAAO,aAAa;;;;;;;;;;;;;;;;;;;;AAiCtB,SAAgB,oBACd,QACA,UACA,UAAsC,EAAE,EACR;CAChC,MAAM,EAAE,iBAAiB,MAAM,kBAAkB,MAAM,aAAa;CACpE,MAAM,mBAAmB,YAAY,gBAAgB;CAErD,MAAM,eACJ,OAAO,WAAW,WAAW,SAAS,mBAAmB,OAAO;CAClE,MAAM,SACJ,OAAO,WAAW,WAAW,YAAY,QAAQ,iBAAiB,GAAG;CAEvE,MAAM,UAAiC;EACrC,QAAQ;EACR,cAAc;EACf;AAED,SAAQ,UAAyB;AAC/B,MAAI,qBAAqB,OAAO,QAAQ,iBAAiB,EAAE;AACzD,OAAI,eACF,OAAM,gBAAgB;AAExB,OAAI,gBACF,OAAM,iBAAiB;AAEzB,YAAS,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;AAyB9B,SAAgB,yBACd,UACA,UAAsC,EAAE,EACR;CAChC,MAAM,EAAE,iBAAiB,MAAM,kBAAkB,MAAM,aAAa;CACpE,MAAM,mBAAmB,YAAY,gBAAgB;CAGrD,MAAM,iBAAiB,OAAO,QAAQ,SAAS,CAC5C,QAAQ,UAA6C,QAAQ,MAAM,GAAG,CAAC,CACvE,KAAK,CAAC,QAAQ,aAAa;EAC1B,MAAM,SAAS,YAAY,QAAkB,iBAAiB;AAK9D,SAAO;GAAE;GAAQ;GAAS,SAJa;IAC7B;IACR,cAAc;IACf;GACkC;GACnC;AAEJ,SAAQ,UAAyB;AAC/B,OAAK,MAAM,EAAE,QAAQ,SAAS,aAAa,eACzC,KAAI,qBAAqB,OAAO,QAAQ,iBAAiB,EAAE;AACzD,OAAI,eACF,OAAM,gBAAgB;AAExB,OAAI,gBACF,OAAM,iBAAiB;AAEzB,WAAQ,OAAO,QAAQ;AACvB;;;;;;;;AAUR,SAAS,mBAAmB,QAA8B;CACxD,MAAM,QAAuB,EAAE;AAE/B,KAAI,OAAO,KAAM,OAAM,KAAK,UAAU;AACtC,KAAI,OAAO,IAAK,OAAM,KAAK,MAAM;AACjC,KAAI,OAAO,MAAO,OAAM,KAAK,QAAQ;AACrC,KAAI,OAAO,KAAM,OAAM,KAAK,OAAO;AACnC,OAAM,KAAK,OAAO,IAAI;AAEtB,QAAO,MAAM,KAAK,IAAI"}