tinykeys 1.3.0 → 2.0.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 CHANGED
@@ -30,7 +30,8 @@ tinykeys(window, {
30
30
  })
31
31
  ```
32
32
 
33
- Alternatively, if you want to only create the keybinding handler, and register it as an event listener yourself:
33
+ Alternatively, if you want to only create the keybinding handler, and register
34
+ it as an event listener yourself:
34
35
 
35
36
  ```js
36
37
  import { createKeybindingsHandler } from "tinykeys"
@@ -161,17 +162,42 @@ Each press can optionally be prefixed with modifier keys:
161
162
 
162
163
  Each press in the sequence must be pressed within 1000ms of the last.
163
164
 
165
+ ### Display the keyboard sequence
166
+
167
+ You can use the `parseKeybinding` method to get a structured representation of a
168
+ keyboard shortcut. It can be useful when you want to display it in a fancier way
169
+ than a plain string.
170
+
171
+ ```js
172
+ import { parseKeybinding } from "tinykeys"
173
+
174
+ let parsedShortcut = parseKeybinding("$mod+Shift+K $mod+1")
175
+ ```
176
+
177
+ Results into:
178
+
179
+ ```js
180
+ ;[
181
+ [["Meta", "Shift"], "K"],
182
+ [["Meta"], "1"],
183
+ ]
184
+ ```
185
+
164
186
  ## Additional Configuration Options
165
187
 
166
188
  You can configure the behavior of tinykeys in a couple ways using a third
167
189
  `options` parameter.
168
190
 
169
191
  ```js
170
- tinykey(window, {
171
- "M": toggleMute,
172
- }, {
173
- event: "keyup",
174
- })
192
+ tinykey(
193
+ window,
194
+ {
195
+ M: toggleMute,
196
+ },
197
+ {
198
+ event: "keyup",
199
+ },
200
+ )
175
201
  ```
176
202
 
177
203
  ### `options.event`
@@ -187,5 +213,5 @@ Key presses will listen to this event (default: `"keydown"`).
187
213
  Keybinding sequences will wait this long between key presses before cancelling
188
214
  (default: `1000`).
189
215
 
190
- > **Note:** Setting this value too low (i.e. `300`) will be too fast for many
191
- > of your users.
216
+ > **Note:** Setting this value too low (i.e. `300`) will be too fast for many of
217
+ > your users.
@@ -1,3 +1,4 @@
1
+ declare type KeyBindingPress = [string[], string];
1
2
  /**
2
3
  * A map of keybinding strings to event handlers.
3
4
  */
@@ -22,15 +23,16 @@ export interface KeyBindingOptions extends KeyBindingHandlerOptions {
22
23
  * Key presses will listen to this event (default: "keydown").
23
24
  */
24
25
  event?: "keydown" | "keyup";
25
- /**
26
- * Keybinding sequences will wait this long between key presses before
27
- * cancelling (default: 1000).
28
- *
29
- * **Note:** Setting this value too low (i.e. `300`) will be too fast for many
30
- * of your users.
31
- */
32
- timeout?: number;
33
26
  }
27
+ /**
28
+ * Parses a "Key Binding String" into its parts
29
+ *
30
+ * grammar = `<sequence>`
31
+ * <sequence> = `<press> <press> <press> ...`
32
+ * <press> = `<key>` or `<mods>+<key>`
33
+ * <mods> = `<mod>+<mod>+...`
34
+ */
35
+ export declare function parseKeybinding(str: string): KeyBindingPress[];
34
36
  /**
35
37
  * Creates an event listener for handling keybindings.
36
38
  *
@@ -61,9 +63,9 @@ export declare function createKeybindingsHandler(keyBindingMap: KeyBindingMap, o
61
63
  *
62
64
  * @example
63
65
  * ```js
64
- * import keybindings from "../src/keybindings"
66
+ * import { tinykeys } from "../src/tinykeys"
65
67
  *
66
- * keybindings(window, {
68
+ * tinykeys(window, {
67
69
  * "Shift+d": () => {
68
70
  * alert("The 'Shift' and 'd' keys were pressed at the same time")
69
71
  * },
@@ -76,4 +78,5 @@ export declare function createKeybindingsHandler(keyBindingMap: KeyBindingMap, o
76
78
  * })
77
79
  * ```
78
80
  */
79
- export default function keybindings(target: Window | HTMLElement, keyBindingMap: KeyBindingMap, options?: KeyBindingOptions): () => void;
81
+ export declare function tinykeys(target: Window | HTMLElement, keyBindingMap: KeyBindingMap, options?: KeyBindingOptions): () => void;
82
+ export {};
package/dist/tinykeys.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=["Shift","Meta","Alt","Control"],t="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function n(e,t){return"function"==typeof e.getModifierState&&e.getModifierState(t)}function r(r,o){var i;void 0===o&&(o={});var a=null!=(i=o.timeout)?i:1e3,u=Object.keys(r).map(function(e){return[(n=e,n.trim().split(" ").map(function(e){var n=e.split(/\b\+/),r=n.pop();return[n=n.map(function(e){return"$mod"===e?t:e}),r]})),r[e]];var n}),c=new Map,f=null;return function(t){t instanceof KeyboardEvent&&(u.forEach(function(r){var o=r[0],i=r[1],a=c.get(o)||o;!function(t,r){return!(r[1].toUpperCase()!==t.key.toUpperCase()&&r[1]!==t.code||r[0].find(function(e){return!n(t,e)})||e.find(function(e){return!r[0].includes(e)&&r[1]!==e&&n(t,e)}))}(t,a[0])?n(t,t.key)||c.delete(o):a.length>1?c.set(o,a.slice(1)):(c.delete(o),i(t))}),f&&clearTimeout(f),f=setTimeout(c.clear.bind(c),a))}}exports.createKeybindingsHandler=r,exports.default=function(e,t,n){var o;void 0===n&&(n={});var i=null!=(o=n.event)?o:"keydown",a=r(t,n);return e.addEventListener(i,a),function(){e.removeEventListener(i,a)}};
1
+ var e=["Shift","Meta","Alt","Control"],t="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function n(e,t){return"function"==typeof e.getModifierState&&e.getModifierState(t)}function r(e){return e.trim().split(" ").map(function(e){var n=e.split(/\b\+/),r=n.pop();return[n=n.map(function(e){return"$mod"===e?t:e}),r]})}function o(t,o){var i;void 0===o&&(o={});var a=null!=(i=o.timeout)?i:1e3,u=Object.keys(t).map(function(e){return[r(e),t[e]]}),c=new Map,f=null;return function(t){t instanceof KeyboardEvent&&(u.forEach(function(r){var o=r[0],i=r[1],a=c.get(o)||o;!function(t,r){return!(r[1].toUpperCase()!==t.key.toUpperCase()&&r[1]!==t.code||r[0].find(function(e){return!n(t,e)})||e.find(function(e){return!r[0].includes(e)&&r[1]!==e&&n(t,e)}))}(t,a[0])?n(t,t.key)||c.delete(o):a.length>1?c.set(o,a.slice(1)):(c.delete(o),i(t))}),f&&clearTimeout(f),f=setTimeout(c.clear.bind(c),a))}}exports.createKeybindingsHandler=o,exports.parseKeybinding=r,exports.tinykeys=function(e,t,n){var r;void 0===n&&(n={});var i=null!=(r=n.event)?r:"keydown",a=o(t,n);return e.addEventListener(i,a),function(){e.removeEventListener(i,a)}};
2
2
  //# sourceMappingURL=tinykeys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tinykeys.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nfunction parse(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parse(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import keybindings from \"../src/keybindings\"\n *\n * keybindings(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport default function keybindings(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","map","key","str","trim","split","press","mods","pop","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","length","set","slice","clearTimeout","setTimeout","clear","bind","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AA4CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YA2EXC,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeM,IAAI,SAAAC,GAChD,MAAO,EAtEMC,EAsECD,EArERC,EACLC,OACAC,MAAM,KACNJ,IAAI,SAAAK,GACJ,IAAIC,EAAOD,EAAMD,MAAM,QACnBH,EAAMK,EAAKC,MAEf,MAAO,CADPD,EAAOA,EAAKN,IAAI,SAAAR,SAAgB,SAARA,EAAiBN,EAAMM,IACjCS,MA8DKP,EAAcO,IAtEpC,IAAeC,IAyEVM,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAOnB,GAIAA,aAAiBoB,gBAIvBd,EAAYe,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAevB,EAAsBc,GAEpC,QAKEA,EAAM,GAAGa,gBAAkB3B,EAAMU,IAAIiB,eACrCb,EAAM,KAAOd,EAAM4B,MAIpBd,EAAM,GAAGe,KAAK,SAAA5B,GACb,OAAQF,EAAiBC,EAAOC,MAMjCP,EAAyBmC,KAAK,SAAA5B,GAC7B,OAAQa,EAAM,GAAGgB,SAAS7B,IAAQa,EAAM,KAAOb,GAAOF,EAAiBC,EAAOC,MAwDhE8B,CAAM/B,EAFOyB,EAAyB,IAU9C1B,EAAiBC,EAAOA,EAAMU,MAClCO,SAAuBM,GAEdE,EAAyBO,OAAS,EAC5Cf,EAAgBgB,IAAIV,EAAUE,EAAyBS,MAAM,KAE7DjB,SAAuBM,GACvBC,EAASxB,MAIPmB,GACHgB,aAAahB,GAGdA,EAAQiB,WAAWnB,EAAgBoB,MAAMC,KAAKrB,GAAkBZ,iEA2BjEkC,EACApC,EACAC,kBAAAA,IAAAA,EAA6B,IAE7B,IAAIJ,WAAQI,EAAQJ,SA/KD,UAgLfwC,EAAatC,EAAyBC,EAAeC,GAIzD,OAFAmC,EAAOE,iBAAiBzC,EAAOwC,cAG9BD,EAAOG,oBAAoB1C,EAAOwC"}
1
+ {"version":3,"file":"tinykeys.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nexport function parseKeybinding(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parseKeybinding(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import { tinykeys } from \"../src/tinykeys\"\n *\n * tinykeys(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport function tinykeys(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","parseKeybinding","str","trim","split","map","press","mods","key","pop","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","length","set","slice","clearTimeout","setTimeout","clear","bind","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AAmCA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YAYXC,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAI,SAAAC,GACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAMD,EAAKE,MAEf,MAAO,CADPF,EAAOA,EAAKF,IAAI,SAAAL,SAAgB,SAARA,EAAiBN,EAAMM,IACjCQ,cAuDDE,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeN,IAAI,SAAAG,GAChD,MAAO,CAACP,EAAgBO,GAAMG,EAAcH,MAGzCS,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAOpB,GAIAA,aAAiBqB,gBAIvBN,EAAYO,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAexB,EAAsBO,GAEpC,QAKEA,EAAM,GAAGqB,gBAAkB5B,EAAMS,IAAImB,eACrCrB,EAAM,KAAOP,EAAM6B,MAIpBtB,EAAM,GAAGuB,KAAK,SAAA7B,GACb,OAAQF,EAAiBC,EAAOC,MAMjCP,EAAyBoC,KAAK,SAAA7B,GAC7B,OAAQM,EAAM,GAAGwB,SAAS9B,IAAQM,EAAM,KAAON,GAAOF,EAAiBC,EAAOC,MAwDhE+B,CAAMhC,EAFO0B,EAAyB,IAU9C3B,EAAiBC,EAAOA,EAAMS,MAClCS,SAAuBM,GAEdE,EAAyBO,OAAS,EAC5Cf,EAAgBgB,IAAIV,EAAUE,EAAyBS,MAAM,KAE7DjB,SAAuBM,GACvBC,EAASzB,MAIPoB,GACHgB,aAAahB,GAGdA,EAAQiB,WAAWnB,EAAgBoB,MAAMC,KAAKrB,GAAkBJ,4FA2BjE0B,EACA5B,EACAC,kBAAAA,IAAAA,EAA6B,IAE7B,IAAIb,WAAQa,EAAQb,SA/KD,UAgLfyC,EAAa9B,EAAyBC,EAAeC,GAIzD,OAFA2B,EAAOE,iBAAiB1C,EAAOyC,cAG9BD,EAAOG,oBAAoB3C,EAAOyC"}
@@ -1,2 +1,2 @@
1
- let e=["Shift","Meta","Alt","Control"],t="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function n(e,t){return"function"==typeof e.getModifierState&&e.getModifierState(t)}function o(o,r={}){var i;let a=null!=(i=r.timeout)?i:1e3,l=Object.keys(o).map(e=>{return[(n=e,n.trim().split(" ").map(e=>{let n=e.split(/\b\+/),o=n.pop();return n=n.map(e=>"$mod"===e?t:e),[n,o]})),o[e]];var n}),u=new Map,d=null;return t=>{t instanceof KeyboardEvent&&(l.forEach(o=>{let r=o[0],i=o[1],a=u.get(r)||r;!function(t,o){return!(o[1].toUpperCase()!==t.key.toUpperCase()&&o[1]!==t.code||o[0].find(e=>!n(t,e))||e.find(e=>!o[0].includes(e)&&o[1]!==e&&n(t,e)))}(t,a[0])?n(t,t.key)||u.delete(r):a.length>1?u.set(r,a.slice(1)):(u.delete(r),i(t))}),d&&clearTimeout(d),d=setTimeout(u.clear.bind(u),a))}}function r(e,t,n={}){var r;let i=null!=(r=n.event)?r:"keydown",a=o(t,n);return e.addEventListener(i,a),()=>{e.removeEventListener(i,a)}}export default r;export{o as createKeybindingsHandler};
1
+ let e=["Shift","Meta","Alt","Control"],t="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function n(e,t){return"function"==typeof e.getModifierState&&e.getModifierState(t)}function o(e){return e.trim().split(" ").map(e=>{let n=e.split(/\b\+/),o=n.pop();return n=n.map(e=>"$mod"===e?t:e),[n,o]})}function r(t,r={}){var i;let a=null!=(i=r.timeout)?i:1e3,l=Object.keys(t).map(e=>[o(e),t[e]]),u=new Map,c=null;return t=>{t instanceof KeyboardEvent&&(l.forEach(o=>{let r=o[0],i=o[1],a=u.get(r)||r;!function(t,o){return!(o[1].toUpperCase()!==t.key.toUpperCase()&&o[1]!==t.code||o[0].find(e=>!n(t,e))||e.find(e=>!o[0].includes(e)&&o[1]!==e&&n(t,e)))}(t,a[0])?n(t,t.key)||u.delete(r):a.length>1?u.set(r,a.slice(1)):(u.delete(r),i(t))}),c&&clearTimeout(c),c=setTimeout(u.clear.bind(u),a))}}function i(e,t,n={}){var o;let i=null!=(o=n.event)?o:"keydown",a=r(t,n);return e.addEventListener(i,a),()=>{e.removeEventListener(i,a)}}export{r as createKeybindingsHandler,o as parseKeybinding,i as tinykeys};
2
2
  //# sourceMappingURL=tinykeys.modern.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tinykeys.modern.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nfunction parse(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parse(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import keybindings from \"../src/keybindings\"\n *\n * keybindings(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport default function keybindings(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","map","key","str","trim","split","press","mods","pop","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","delete","length","set","slice","clearTimeout","setTimeout","clear","bind","keybindings","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AA4CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YA2EXC,EACfC,EACAC,EAAoC,UAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeM,IAAIC,IAChD,MAAO,EAtEMC,EAsECD,EArERC,EACLC,OACAC,MAAM,KACNJ,IAAIK,IACJ,IAAIC,EAAOD,EAAMD,MAAM,QACnBH,EAAMK,EAAKC,MAEf,OADAD,EAAOA,EAAKN,IAAIR,GAAgB,SAARA,EAAiBN,EAAMM,GACxC,CAACc,EAAML,MA8DKP,EAAcO,IAtEpC,IAAeC,IAyEVM,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,OAAOnB,IAIAA,aAAiBoB,gBAIvBd,EAAYe,QAAQC,IACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAevB,EAAsBc,GAEpC,QAKEA,EAAM,GAAGa,gBAAkB3B,EAAMU,IAAIiB,eACrCb,EAAM,KAAOd,EAAM4B,MAIpBd,EAAM,GAAGe,KAAK5B,IACLF,EAAiBC,EAAOC,KAMjCP,EAAyBmC,KAAK5B,IACrBa,EAAM,GAAGgB,SAAS7B,IAAQa,EAAM,KAAOb,GAAOF,EAAiBC,EAAOC,KAwDhE8B,CAAM/B,EAFOyB,EAAyB,IAU9C1B,EAAiBC,EAAOA,EAAMU,MAClCO,EAAgBe,OAAOT,GAEdE,EAAyBQ,OAAS,EAC5ChB,EAAgBiB,IAAIX,EAAUE,EAAyBU,MAAM,KAE7DlB,EAAgBe,OAAOT,GACvBC,EAASxB,MAIPmB,GACHiB,aAAajB,GAGdA,EAAQkB,WAAWpB,EAAgBqB,MAAMC,KAAKtB,GAAkBZ,cA0B1CmC,EACvBC,EACAtC,EACAC,EAA6B,UAE7B,IAAIJ,WAAQI,EAAQJ,SA/KD,UAgLf0C,EAAaxC,EAAyBC,EAAeC,GAIzD,OAFAqC,EAAOE,iBAAiB3C,EAAO0C,GAExB,KACND,EAAOG,oBAAoB5C,EAAO0C"}
1
+ {"version":3,"file":"tinykeys.modern.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nexport function parseKeybinding(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parseKeybinding(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import { tinykeys } from \"../src/tinykeys\"\n *\n * tinykeys(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport function tinykeys(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","parseKeybinding","str","trim","split","map","press","mods","key","pop","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","delete","length","set","slice","clearTimeout","setTimeout","clear","bind","tinykeys","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AAmCA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YAYXC,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAIC,IACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAMD,EAAKE,MAEf,OADAF,EAAOA,EAAKF,IAAIL,GAAgB,SAARA,EAAiBN,EAAMM,GACxC,CAACO,EAAMC,cAuDDE,EACfC,EACAC,EAAoC,UAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeN,IAAIG,GACzC,CAACP,EAAgBO,GAAMG,EAAcH,KAGzCS,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,OAAOpB,IAIAA,aAAiBqB,gBAIvBN,EAAYO,QAAQC,IACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAexB,EAAsBO,GAEpC,QAKEA,EAAM,GAAGqB,gBAAkB5B,EAAMS,IAAImB,eACrCrB,EAAM,KAAOP,EAAM6B,MAIpBtB,EAAM,GAAGuB,KAAK7B,IACLF,EAAiBC,EAAOC,KAMjCP,EAAyBoC,KAAK7B,IACrBM,EAAM,GAAGwB,SAAS9B,IAAQM,EAAM,KAAON,GAAOF,EAAiBC,EAAOC,KAwDhE+B,CAAMhC,EAFO0B,EAAyB,IAU9C3B,EAAiBC,EAAOA,EAAMS,MAClCS,EAAgBe,OAAOT,GAEdE,EAAyBQ,OAAS,EAC5ChB,EAAgBiB,IAAIX,EAAUE,EAAyBU,MAAM,KAE7DlB,EAAgBe,OAAOT,GACvBC,EAASzB,MAIPoB,GACHiB,aAAajB,GAGdA,EAAQkB,WAAWpB,EAAgBqB,MAAMC,KAAKtB,GAAkBJ,cA0BlD2B,EACfC,EACA9B,EACAC,EAA6B,UAE7B,IAAIb,WAAQa,EAAQb,SA/KD,UAgLf2C,EAAahC,EAAyBC,EAAeC,GAIzD,OAFA6B,EAAOE,iBAAiB5C,EAAO2C,GAExB,KACND,EAAOG,oBAAoB7C,EAAO2C"}
@@ -1,2 +1,2 @@
1
- var t=["Shift","Meta","Alt","Control"],e="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function n(t,e){return"function"==typeof t.getModifierState&&t.getModifierState(e)}function r(r,o){var i;void 0===o&&(o={});var a=null!=(i=o.timeout)?i:1e3,u=Object.keys(r).map(function(t){return[(n=t,n.trim().split(" ").map(function(t){var n=t.split(/\b\+/),r=n.pop();return[n=n.map(function(t){return"$mod"===t?e:t}),r]})),r[t]];var n}),f=new Map,c=null;return function(e){e instanceof KeyboardEvent&&(u.forEach(function(r){var o=r[0],i=r[1],a=f.get(o)||o;!function(e,r){return!(r[1].toUpperCase()!==e.key.toUpperCase()&&r[1]!==e.code||r[0].find(function(t){return!n(e,t)})||t.find(function(t){return!r[0].includes(t)&&r[1]!==t&&n(e,t)}))}(e,a[0])?n(e,e.key)||f.delete(o):a.length>1?f.set(o,a.slice(1)):(f.delete(o),i(e))}),c&&clearTimeout(c),c=setTimeout(f.clear.bind(f),a))}}function o(t,e,n){var o;void 0===n&&(n={});var i=null!=(o=n.event)?o:"keydown",a=r(e,n);return t.addEventListener(i,a),function(){t.removeEventListener(i,a)}}export default o;export{r as createKeybindingsHandler};
1
+ var t=["Shift","Meta","Alt","Control"],e="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function n(t,e){return"function"==typeof t.getModifierState&&t.getModifierState(e)}function r(t){return t.trim().split(" ").map(function(t){var n=t.split(/\b\+/),r=n.pop();return[n=n.map(function(t){return"$mod"===t?e:t}),r]})}function o(e,o){var i;void 0===o&&(o={});var u=null!=(i=o.timeout)?i:1e3,a=Object.keys(e).map(function(t){return[r(t),e[t]]}),c=new Map,f=null;return function(e){e instanceof KeyboardEvent&&(a.forEach(function(r){var o=r[0],i=r[1],u=c.get(o)||o;!function(e,r){return!(r[1].toUpperCase()!==e.key.toUpperCase()&&r[1]!==e.code||r[0].find(function(t){return!n(e,t)})||t.find(function(t){return!r[0].includes(t)&&r[1]!==t&&n(e,t)}))}(e,u[0])?n(e,e.key)||c.delete(o):u.length>1?c.set(o,u.slice(1)):(c.delete(o),i(e))}),f&&clearTimeout(f),f=setTimeout(c.clear.bind(c),u))}}function i(t,e,n){var r;void 0===n&&(n={});var i=null!=(r=n.event)?r:"keydown",u=o(e,n);return t.addEventListener(i,u),function(){t.removeEventListener(i,u)}}export{o as createKeybindingsHandler,r as parseKeybinding,i as tinykeys};
2
2
  //# sourceMappingURL=tinykeys.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tinykeys.module.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nfunction parse(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parse(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import keybindings from \"../src/keybindings\"\n *\n * keybindings(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport default function keybindings(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","map","key","str","trim","split","press","mods","pop","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","length","set","slice","clearTimeout","setTimeout","clear","bind","keybindings","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AA4CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YA2EXC,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeM,IAAI,SAAAC,GAChD,MAAO,EAtEMC,EAsECD,EArERC,EACLC,OACAC,MAAM,KACNJ,IAAI,SAAAK,GACJ,IAAIC,EAAOD,EAAMD,MAAM,QACnBH,EAAMK,EAAKC,MAEf,MAAO,CADPD,EAAOA,EAAKN,IAAI,SAAAR,SAAgB,SAARA,EAAiBN,EAAMM,IACjCS,MA8DKP,EAAcO,IAtEpC,IAAeC,IAyEVM,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAOnB,GAIAA,aAAiBoB,gBAIvBd,EAAYe,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAevB,EAAsBc,GAEpC,QAKEA,EAAM,GAAGa,gBAAkB3B,EAAMU,IAAIiB,eACrCb,EAAM,KAAOd,EAAM4B,MAIpBd,EAAM,GAAGe,KAAK,SAAA5B,GACb,OAAQF,EAAiBC,EAAOC,MAMjCP,EAAyBmC,KAAK,SAAA5B,GAC7B,OAAQa,EAAM,GAAGgB,SAAS7B,IAAQa,EAAM,KAAOb,GAAOF,EAAiBC,EAAOC,MAwDhE8B,CAAM/B,EAFOyB,EAAyB,IAU9C1B,EAAiBC,EAAOA,EAAMU,MAClCO,SAAuBM,GAEdE,EAAyBO,OAAS,EAC5Cf,EAAgBgB,IAAIV,EAAUE,EAAyBS,MAAM,KAE7DjB,SAAuBM,GACvBC,EAASxB,MAIPmB,GACHgB,aAAahB,GAGdA,EAAQiB,WAAWnB,EAAgBoB,MAAMC,KAAKrB,GAAkBZ,cA0B1CkC,EACvBC,EACArC,EACAC,kBAAAA,IAAAA,EAA6B,IAE7B,IAAIJ,WAAQI,EAAQJ,SA/KD,UAgLfyC,EAAavC,EAAyBC,EAAeC,GAIzD,OAFAoC,EAAOE,iBAAiB1C,EAAOyC,cAG9BD,EAAOG,oBAAoB3C,EAAOyC"}
1
+ {"version":3,"file":"tinykeys.module.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nexport function parseKeybinding(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parseKeybinding(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import { tinykeys } from \"../src/tinykeys\"\n *\n * tinykeys(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport function tinykeys(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","parseKeybinding","str","trim","split","map","press","mods","key","pop","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","length","set","slice","clearTimeout","setTimeout","clear","bind","tinykeys","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AAmCA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YAYXC,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAI,SAAAC,GACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAMD,EAAKE,MAEf,MAAO,CADPF,EAAOA,EAAKF,IAAI,SAAAL,SAAgB,SAARA,EAAiBN,EAAMM,IACjCQ,cAuDDE,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeN,IAAI,SAAAG,GAChD,MAAO,CAACP,EAAgBO,GAAMG,EAAcH,MAGzCS,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAOpB,GAIAA,aAAiBqB,gBAIvBN,EAAYO,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAexB,EAAsBO,GAEpC,QAKEA,EAAM,GAAGqB,gBAAkB5B,EAAMS,IAAImB,eACrCrB,EAAM,KAAOP,EAAM6B,MAIpBtB,EAAM,GAAGuB,KAAK,SAAA7B,GACb,OAAQF,EAAiBC,EAAOC,MAMjCP,EAAyBoC,KAAK,SAAA7B,GAC7B,OAAQM,EAAM,GAAGwB,SAAS9B,IAAQM,EAAM,KAAON,GAAOF,EAAiBC,EAAOC,MAwDhE+B,CAAMhC,EAFO0B,EAAyB,IAU9C3B,EAAiBC,EAAOA,EAAMS,MAClCS,SAAuBM,GAEdE,EAAyBO,OAAS,EAC5Cf,EAAgBgB,IAAIV,EAAUE,EAAyBS,MAAM,KAE7DjB,SAAuBM,GACvBC,EAASzB,MAIPoB,GACHgB,aAAahB,GAGdA,EAAQiB,WAAWnB,EAAgBoB,MAAMC,KAAKrB,GAAkBJ,cA0BlD0B,EACfC,EACA7B,EACAC,kBAAAA,IAAAA,EAA6B,IAE7B,IAAIb,WAAQa,EAAQb,SA/KD,UAgLf0C,EAAa/B,EAAyBC,EAAeC,GAIzD,OAFA4B,EAAOE,iBAAiB3C,EAAO0C,cAG9BD,EAAOG,oBAAoB5C,EAAO0C"}
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e||self).tinykeys={})}(this,function(e){var t=["Shift","Meta","Alt","Control"],n="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function o(e,t){return"function"==typeof e.getModifierState&&e.getModifierState(t)}function i(e,i){var r;void 0===i&&(i={});var a=null!=(r=i.timeout)?r:1e3,f=Object.keys(e).map(function(t){return[(o=t,o.trim().split(" ").map(function(e){var t=e.split(/\b\+/),o=t.pop();return[t=t.map(function(e){return"$mod"===e?n:e}),o]})),e[t]];var o}),u=new Map,d=null;return function(e){e instanceof KeyboardEvent&&(f.forEach(function(n){var i=n[0],r=n[1],a=u.get(i)||i;!function(e,n){return!(n[1].toUpperCase()!==e.key.toUpperCase()&&n[1]!==e.code||n[0].find(function(t){return!o(e,t)})||t.find(function(t){return!n[0].includes(t)&&n[1]!==t&&o(e,t)}))}(e,a[0])?o(e,e.key)||u.delete(i):a.length>1?u.set(i,a.slice(1)):(u.delete(i),r(e))}),d&&clearTimeout(d),d=setTimeout(u.clear.bind(u),a))}}e.createKeybindingsHandler=i,e.default=function(e,t,n){var o;void 0===n&&(n={});var r=null!=(o=n.event)?o:"keydown",a=i(t,n);return e.addEventListener(r,a),function(){e.removeEventListener(r,a)}}});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e||self).tinykeys={})}(this,function(e){var t=["Shift","Meta","Alt","Control"],n="object"==typeof navigator&&/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"Meta":"Control";function i(e,t){return"function"==typeof e.getModifierState&&e.getModifierState(t)}function o(e){return e.trim().split(" ").map(function(e){var t=e.split(/\b\+/),i=t.pop();return[t=t.map(function(e){return"$mod"===e?n:e}),i]})}function r(e,n){var r;void 0===n&&(n={});var f=null!=(r=n.timeout)?r:1e3,u=Object.keys(e).map(function(t){return[o(t),e[t]]}),a=new Map,d=null;return function(e){e instanceof KeyboardEvent&&(u.forEach(function(n){var o=n[0],r=n[1],f=a.get(o)||o;!function(e,n){return!(n[1].toUpperCase()!==e.key.toUpperCase()&&n[1]!==e.code||n[0].find(function(t){return!i(e,t)})||t.find(function(t){return!n[0].includes(t)&&n[1]!==t&&i(e,t)}))}(e,f[0])?i(e,e.key)||a.delete(o):f.length>1?a.set(o,f.slice(1)):(a.delete(o),r(e))}),d&&clearTimeout(d),d=setTimeout(a.clear.bind(a),f))}}e.createKeybindingsHandler=r,e.parseKeybinding=o,e.tinykeys=function(e,t,n){var i;void 0===n&&(n={});var o=null!=(i=n.event)?i:"keydown",f=r(t,n);return e.addEventListener(o,f),function(){e.removeEventListener(o,f)}}});
2
2
  //# sourceMappingURL=tinykeys.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tinykeys.umd.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nfunction parse(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parse(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import keybindings from \"../src/keybindings\"\n *\n * keybindings(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport default function keybindings(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","map","key","str","trim","split","press","mods","pop","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","length","set","slice","clearTimeout","setTimeout","clear","bind","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"kOA4CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YA2EXC,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeM,IAAI,SAAAC,GAChD,MAAO,EAtEMC,EAsECD,EArERC,EACLC,OACAC,MAAM,KACNJ,IAAI,SAAAK,GACJ,IAAIC,EAAOD,EAAMD,MAAM,QACnBH,EAAMK,EAAKC,MAEf,MAAO,CADPD,EAAOA,EAAKN,IAAI,SAAAR,SAAgB,SAARA,EAAiBN,EAAMM,IACjCS,MA8DKP,EAAcO,IAtEpC,IAAeC,IAyEVM,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAOnB,GAIAA,aAAiBoB,gBAIvBd,EAAYe,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAevB,EAAsBc,GAEpC,QAKEA,EAAM,GAAGa,gBAAkB3B,EAAMU,IAAIiB,eACrCb,EAAM,KAAOd,EAAM4B,MAIpBd,EAAM,GAAGe,KAAK,SAAA5B,GACb,OAAQF,EAAiBC,EAAOC,MAMjCP,EAAyBmC,KAAK,SAAA5B,GAC7B,OAAQa,EAAM,GAAGgB,SAAS7B,IAAQa,EAAM,KAAOb,GAAOF,EAAiBC,EAAOC,MAwDhE8B,CAAM/B,EAFOyB,EAAyB,IAU9C1B,EAAiBC,EAAOA,EAAMU,MAClCO,SAAuBM,GAEdE,EAAyBO,OAAS,EAC5Cf,EAAgBgB,IAAIV,EAAUE,EAAyBS,MAAM,KAE7DjB,SAAuBM,GACvBC,EAASxB,MAIPmB,GACHgB,aAAahB,GAGdA,EAAQiB,WAAWnB,EAAgBoB,MAAMC,KAAKrB,GAAkBZ,qDA2BjEkC,EACApC,EACAC,kBAAAA,IAAAA,EAA6B,IAE7B,IAAIJ,WAAQI,EAAQJ,SA/KD,UAgLfwC,EAAatC,EAAyBC,EAAeC,GAIzD,OAFAmC,EAAOE,iBAAiBzC,EAAOwC,cAG9BD,EAAOG,oBAAoB1C,EAAOwC"}
1
+ {"version":3,"file":"tinykeys.umd.js","sources":["../src/tinykeys.ts"],"sourcesContent":["type KeyBindingPress = [string[], string]\n\n/**\n * A map of keybinding strings to event handlers.\n */\nexport interface KeyBindingMap {\n\t[keybinding: string]: (event: KeyboardEvent) => void\n}\n\nexport interface KeyBindingHandlerOptions {\n\t/**\n\t * Keybinding sequences will wait this long between key presses before\n\t * cancelling (default: 1000).\n\t *\n\t * **Note:** Setting this value too low (i.e. `300`) will be too fast for many\n\t * of your users.\n\t */\n\ttimeout?: number\n}\n\n/**\n * Options to configure the behavior of keybindings.\n */\nexport interface KeyBindingOptions extends KeyBindingHandlerOptions {\n\t/**\n\t * Key presses will listen to this event (default: \"keydown\").\n\t */\n\tevent?: \"keydown\" | \"keyup\"\n}\n\n/**\n * These are the modifier keys that change the meaning of keybindings.\n *\n * Note: Ignoring \"AltGraph\" because it is covered by the others.\n */\nlet KEYBINDING_MODIFIER_KEYS = [\"Shift\", \"Meta\", \"Alt\", \"Control\"]\n\n/**\n * Keybinding sequences should timeout if individual key presses are more than\n * 1s apart by default.\n */\nlet DEFAULT_TIMEOUT = 1000\n\n/**\n * Keybinding sequences should bind to this event by default.\n */\nlet DEFAULT_EVENT = \"keydown\"\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD =\n\ttypeof navigator === \"object\" &&\n\t/Mac|iPod|iPhone|iPad/.test(navigator.platform)\n\t\t? \"Meta\"\n\t\t: \"Control\"\n\n/**\n * There's a bug in Chrome that causes event.getModifierState not to exist on\n * KeyboardEvent's for F1/F2/etc keys.\n */\nfunction getModifierState(event: KeyboardEvent, mod: string) {\n\treturn typeof event.getModifierState === \"function\"\n\t\t? event.getModifierState(mod)\n\t\t: false\n}\n\n/**\n * Parses a \"Key Binding String\" into its parts\n *\n * grammar = `<sequence>`\n * <sequence> = `<press> <press> <press> ...`\n * <press> = `<key>` or `<mods>+<key>`\n * <mods> = `<mod>+<mod>+...`\n */\nexport function parseKeybinding(str: string): KeyBindingPress[] {\n\treturn str\n\t\t.trim()\n\t\t.split(\" \")\n\t\t.map(press => {\n\t\t\tlet mods = press.split(/\\b\\+/)\n\t\t\tlet key = mods.pop() as string\n\t\t\tmods = mods.map(mod => (mod === \"$mod\" ? MOD : mod))\n\t\t\treturn [mods, key]\n\t\t})\n}\n\n/**\n * This tells us if a series of events matches a key binding sequence either\n * partially or exactly.\n */\nfunction match(event: KeyboardEvent, press: KeyBindingPress): boolean {\n\t// prettier-ignore\n\treturn !(\n\t\t// Allow either the `event.key` or the `event.code`\n\t\t// MDN event.key: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n\t\t// MDN event.code: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\t\t(\n\t\t\tpress[1].toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tpress[1] !== event.code\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tpress[0].find(mod => {\n\t\t\treturn !getModifierState(event, mod)\n\t\t}) ||\n\n\t\t// KEYBINDING_MODIFIER_KEYS (Shift/Control/etc) change the meaning of a\n\t\t// keybinding. So if they are pressed but aren't part of the current\n\t\t// keybinding press, then we don't have a match.\n\t\tKEYBINDING_MODIFIER_KEYS.find(mod => {\n\t\t\treturn !press[0].includes(mod) && press[1] !== mod && getModifierState(event, mod)\n\t\t})\n\t)\n}\n\n/**\n * Creates an event listener for handling keybindings.\n *\n * @example\n * ```js\n * import { createKeybindingsHandler } from \"../src/keybindings\"\n *\n * let handler = createKeybindingsHandler({\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n *\n * window.addEvenListener(\"keydown\", handler)\n * ```\n */\nexport function createKeybindingsHandler(\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingHandlerOptions = {},\n): EventListener {\n\tlet timeout = options.timeout ?? DEFAULT_TIMEOUT\n\n\tlet keyBindings = Object.keys(keyBindingMap).map(key => {\n\t\treturn [parseKeybinding(key), keyBindingMap[key]] as const\n\t})\n\n\tlet possibleMatches = new Map<KeyBindingPress[], KeyBindingPress[]>()\n\tlet timer: number | null = null\n\n\treturn event => {\n\t\t// Ensure and stop any event that isn't a full keyboard event.\n\t\t// Autocomplete option navigation and selection would fire a instanceof Event,\n\t\t// instead of the expected KeyboardEvent\n\t\tif (!(event instanceof KeyboardEvent)) {\n\t\t\treturn\n\t\t}\n\n\t\tkeyBindings.forEach(keyBinding => {\n\t\t\tlet sequence = keyBinding[0]\n\t\t\tlet callback = keyBinding[1]\n\n\t\t\tlet prev = possibleMatches.get(sequence)\n\t\t\tlet remainingExpectedPresses = prev ? prev : sequence\n\t\t\tlet currentExpectedPress = remainingExpectedPresses[0]\n\n\t\t\tlet matches = match(event, currentExpectedPress)\n\n\t\t\tif (!matches) {\n\t\t\t\t// Modifier keydown events shouldn't break sequences\n\t\t\t\t// Note: This works because:\n\t\t\t\t// - non-modifiers will always return false\n\t\t\t\t// - if the current keypress is a modifier then it will return true when we check its state\n\t\t\t\t// MDN: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState\n\t\t\t\tif (!getModifierState(event, event.key)) {\n\t\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\t}\n\t\t\t} else if (remainingExpectedPresses.length > 1) {\n\t\t\t\tpossibleMatches.set(sequence, remainingExpectedPresses.slice(1))\n\t\t\t} else {\n\t\t\t\tpossibleMatches.delete(sequence)\n\t\t\t\tcallback(event)\n\t\t\t}\n\t\t})\n\n\t\tif (timer) {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\ttimer = setTimeout(possibleMatches.clear.bind(possibleMatches), timeout)\n\t}\n}\n\n/**\n * Subscribes to keybindings.\n *\n * Returns an unsubscribe method.\n *\n * @example\n * ```js\n * import { tinykeys } from \"../src/tinykeys\"\n *\n * tinykeys(window, {\n * \t\"Shift+d\": () => {\n * \t\talert(\"The 'Shift' and 'd' keys were pressed at the same time\")\n * \t},\n * \t\"y e e t\": () => {\n * \t\talert(\"The keys 'y', 'e', 'e', and 't' were pressed in order\")\n * \t},\n * \t\"$mod+d\": () => {\n * \t\talert(\"Either 'Control+d' or 'Meta+d' were pressed\")\n * \t},\n * })\n * ```\n */\nexport function tinykeys(\n\ttarget: Window | HTMLElement,\n\tkeyBindingMap: KeyBindingMap,\n\toptions: KeyBindingOptions = {},\n): () => void {\n\tlet event = options.event ?? DEFAULT_EVENT\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, options)\n\n\ttarget.addEventListener(event, onKeyEvent)\n\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","MOD","navigator","test","platform","getModifierState","event","mod","parseKeybinding","str","trim","split","map","press","mods","key","pop","createKeybindingsHandler","keyBindingMap","options","timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","toUpperCase","code","find","includes","match","length","set","slice","clearTimeout","setTimeout","clear","bind","target","onKeyEvent","addEventListener","removeEventListener"],"mappings":"kOAmCA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAgBpDC,EACkB,iBAAdC,WACP,uBAAuBC,KAAKD,UAAUE,UACnC,OACA,UAMJ,SAASC,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,kBACjBC,EAAMD,iBAAiBE,YAYXC,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAI,SAAAC,GACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAMD,EAAKE,MAEf,MAAO,CADPF,EAAOA,EAAKF,IAAI,SAAAL,SAAgB,SAARA,EAAiBN,EAAMM,IACjCQ,cAuDDE,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,WAAUD,EAAQC,WArGD,IAuGjBC,EAAcC,OAAOC,KAAKL,GAAeN,IAAI,SAAAG,GAChD,MAAO,CAACP,EAAgBO,GAAMG,EAAcH,MAGzCS,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAOpB,GAIAA,aAAiBqB,gBAIvBN,EAAYO,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,GAzEhD,SAAexB,EAAsBO,GAEpC,QAKEA,EAAM,GAAGqB,gBAAkB5B,EAAMS,IAAImB,eACrCrB,EAAM,KAAOP,EAAM6B,MAIpBtB,EAAM,GAAGuB,KAAK,SAAA7B,GACb,OAAQF,EAAiBC,EAAOC,MAMjCP,EAAyBoC,KAAK,SAAA7B,GAC7B,OAAQM,EAAM,GAAGwB,SAAS9B,IAAQM,EAAM,KAAON,GAAOF,EAAiBC,EAAOC,MAwDhE+B,CAAMhC,EAFO0B,EAAyB,IAU9C3B,EAAiBC,EAAOA,EAAMS,MAClCS,SAAuBM,GAEdE,EAAyBO,OAAS,EAC5Cf,EAAgBgB,IAAIV,EAAUE,EAAyBS,MAAM,KAE7DjB,SAAuBM,GACvBC,EAASzB,MAIPoB,GACHgB,aAAahB,GAGdA,EAAQiB,WAAWnB,EAAgBoB,MAAMC,KAAKrB,GAAkBJ,0EA2BjE0B,EACA5B,EACAC,kBAAAA,IAAAA,EAA6B,IAE7B,IAAIb,WAAQa,EAAQb,SA/KD,UAgLfyC,EAAa9B,EAAyBC,EAAeC,GAIzD,OAFA2B,EAAOE,iBAAiB1C,EAAOyC,cAG9BD,EAAOG,oBAAoB3C,EAAOyC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinykeys",
3
- "version": "1.3.0",
3
+ "version": "2.0.0",
4
4
  "description": "A tiny (~400 B) & modern library for keybindings.",
5
5
  "author": "Jamie Kyle <me@thejameskyle.com>",
6
6
  "license": "MIT",
@@ -80,5 +80,6 @@
80
80
  "require": [
81
81
  "ts-node/register"
82
82
  ]
83
- }
83
+ },
84
+ "dependencies": {}
84
85
  }