tinykeys 2.0.0 → 3.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
@@ -1,6 +1,6 @@
1
1
  # `tinykeys`
2
2
 
3
- > A tiny (~400 B) & modern library for keybindings.
3
+ > A tiny (~650 B) & modern library for keybindings.
4
4
  > [See Demo](https://jamiebuilds.github.io/tinykeys/)
5
5
 
6
6
  ## Install
@@ -14,7 +14,7 @@ Or for a CDN version, you can use it on [unpkg.com](https://unpkg.com/tinykeys)
14
14
  ## Usage
15
15
 
16
16
  ```js
17
- import tinykeys from "tinykeys" // Or `window.tinykeys` using the CDN version
17
+ import { tinykeys } from "tinykeys" // Or `window.tinykeys` using the CDN version
18
18
 
19
19
  tinykeys(window, {
20
20
  "Shift+D": () => {
@@ -23,9 +23,9 @@ tinykeys(window, {
23
23
  "y e e t": () => {
24
24
  alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
25
25
  },
26
- "$mod+KeyD": event => {
26
+ "$mod+([0-9])": event => {
27
27
  event.preventDefault()
28
- alert("Either 'Control+d' or 'Meta+d' were pressed")
28
+ alert(`Either 'Control+${event.key}' or 'Meta+${event.key}' were pressed`)
29
29
  },
30
30
  })
31
31
  ```
@@ -59,7 +59,7 @@ returned `unsubscribe()` function.
59
59
 
60
60
  ```js
61
61
  import { useEffect } from "react"
62
- import tinykeys from "tinykeys"
62
+ import { tinykeys } from "tinykeys"
63
63
 
64
64
  function useKeyboardShortcuts() {
65
65
  useEffect(() => {
@@ -102,6 +102,37 @@ Something missing? Check out the key logger on the
102
102
  > same key on the keyboard. Keep in mind how this is affected by international
103
103
  > keyboards which may have different layouts._
104
104
 
105
+ ## Key aliases
106
+
107
+ In some instances, tinykeys will alias keys depending on the platform to
108
+ simplify cross-platform keybindings on international keyboards.
109
+
110
+ ### `AltGraph` (modifier)
111
+
112
+ On Windows, on many non-US standard keyboard layouts, there is a key named
113
+ `Alt Gr` or `AltGraph` in the browser, in some browsers, pressing `Control+Alt`
114
+ will report `AltGraph` as being pressed instead.
115
+
116
+ Similarly on macOS, the `Alt` (`Option`) key will sometimes be reported as the
117
+ `AltGraph` key.
118
+
119
+ **Note:** The purpose of the `Alt Gr` key is to type "Alternate Graphics" so you
120
+ will often want to use the `event.code` (`KeyS`) for letters instead of
121
+ `event.key` (`S`)
122
+
123
+ ```js
124
+ tinykeys(window, {
125
+ "Control+Alt+KeyS": event => {
126
+ // macOS: `Control+Alt+S` or `Control+AltGraph+S`
127
+ // Windows: `Control+Alt+S` or `Control+AltGraph+S` or `AltGraph+S`
128
+ },
129
+ "$mod+Alt+KeyS": event => {
130
+ // macOS: `Meta+Alt+S` or `Meta+AltGraph+S`
131
+ // Windows: `Control+Alt+S` or `Control+AltGraph+S` or `AltGraph+S`
132
+ },
133
+ })
134
+ ```
135
+
105
136
  ## Keybinding Syntax
106
137
 
107
138
  Keybindings are made up of a **_sequence_** of **_presses_**.
@@ -142,6 +173,14 @@ platform keybindings:
142
173
  "$mod+Shift+D" // Meta/Control+Shift+D
143
174
  ```
144
175
 
176
+ Alternatively, you can use parenthesis to use case-sensitive regular expressions
177
+ to match multiple keys.
178
+
179
+ ```js
180
+ "$mod+([0-9])" // $mod+0, $mod+1, $mod+2, etc...
181
+ // equivalent regex: /^[0-9]$/
182
+ ```
183
+
145
184
  ### Keybinding Sequences
146
185
 
147
186
  Keybindings can also consist of several key presses in a row:
@@ -176,8 +215,9 @@ let parsedShortcut = parseKeybinding("$mod+Shift+K $mod+1")
176
215
 
177
216
  Results into:
178
217
 
218
+ <!-- prettier-ignore -->
179
219
  ```js
180
- ;[
220
+ [
181
221
  [["Meta", "Shift"], "K"],
182
222
  [["Meta"], "1"],
183
223
  ]
@@ -189,13 +229,14 @@ You can configure the behavior of tinykeys in a couple ways using a third
189
229
  `options` parameter.
190
230
 
191
231
  ```js
192
- tinykey(
232
+ tinykeys(
193
233
  window,
194
234
  {
195
235
  M: toggleMute,
196
236
  },
197
237
  {
198
238
  event: "keyup",
239
+ capture: true,
199
240
  },
200
241
  )
201
242
  ```
@@ -1,4 +1,7 @@
1
- declare type KeyBindingPress = [string[], string];
1
+ /**
2
+ * A single press of a keybinding sequence
3
+ */
4
+ export declare type KeyBindingPress = [mods: string[], key: string | RegExp];
2
5
  /**
3
6
  * A map of keybinding strings to event handlers.
4
7
  */
@@ -23,6 +26,10 @@ export interface KeyBindingOptions extends KeyBindingHandlerOptions {
23
26
  * Key presses will listen to this event (default: "keydown").
24
27
  */
25
28
  event?: "keydown" | "keyup";
29
+ /**
30
+ * Key presses will use a capture listener (default: false)
31
+ */
32
+ capture?: boolean;
26
33
  }
27
34
  /**
28
35
  * Parses a "Key Binding String" into its parts
@@ -31,8 +38,14 @@ export interface KeyBindingOptions extends KeyBindingHandlerOptions {
31
38
  * <sequence> = `<press> <press> <press> ...`
32
39
  * <press> = `<key>` or `<mods>+<key>`
33
40
  * <mods> = `<mod>+<mod>+...`
41
+ * <key> = `<KeyboardEvent.key>` or `<KeyboardEvent.code>` (case-insensitive)
42
+ * <key> = `(<regex>)` -> `/^<regex>$/` (case-sensitive)
34
43
  */
35
44
  export declare function parseKeybinding(str: string): KeyBindingPress[];
45
+ /**
46
+ * This tells us if a single keyboard event matches a single keybinding press.
47
+ */
48
+ export declare function matchKeyBindingPress(event: KeyboardEvent, [mods, key]: KeyBindingPress): boolean;
36
49
  /**
37
50
  * Creates an event listener for handling keybindings.
38
51
  *
@@ -78,5 +91,4 @@ export declare function createKeybindingsHandler(keyBindingMap: KeyBindingMap, o
78
91
  * })
79
92
  * ```
80
93
  */
81
- export declare function tinykeys(target: Window | HTMLElement, keyBindingMap: KeyBindingMap, options?: KeyBindingOptions): () => void;
82
- export {};
94
+ export declare function tinykeys(target: Window | HTMLElement, keyBindingMap: KeyBindingMap, { event, capture, timeout }?: KeyBindingOptions): () => void;
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(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)}};
1
+ var e=["Shift","Meta","Alt","Control"],t="object"==typeof navigator?navigator.platform:"",n=/Mac|iPod|iPhone|iPad/.test(t),r=n?"Meta":"Control",i="Win32"===t?["Control","Alt"]:n?["Alt"]:[];function o(e,t){return"function"==typeof e.getModifierState&&(e.getModifierState(t)||i.includes(t)&&e.getModifierState("AltGraph"))}function a(e){return e.trim().split(" ").map(function(e){var t=e.split(/\b\+/),n=t.pop(),i=n.match(/^\((.+)\)$/);return i&&(n=new RegExp("^"+i[1]+"$")),[t=t.map(function(e){return"$mod"===e?r:e}),n]})}function u(t,n){var r=n[0],i=n[1];return!((i instanceof RegExp?!i.test(t.key)&&!i.test(t.code):i.toUpperCase()!==t.key.toUpperCase()&&i!==t.code)||r.find(function(e){return!o(t,e)})||e.find(function(e){return!r.includes(e)&&i!==e&&o(t,e)}))}function c(e,t){var n;void 0===t&&(t={});var r=null!=(n=t.timeout)?n:1e3,i=Object.keys(e).map(function(t){return[a(t),e[t]]}),c=new Map,d=null;return function(e){e instanceof KeyboardEvent&&(i.forEach(function(t){var n=t[0],r=t[1],i=c.get(n)||n;u(e,i[0])?i.length>1?c.set(n,i.slice(1)):(c.delete(n),r(e)):o(e,e.key)||c.delete(n)}),d&&clearTimeout(d),d=setTimeout(c.clear.bind(c),r))}}exports.createKeybindingsHandler=c,exports.matchKeyBindingPress=u,exports.parseKeybinding=a,exports.tinykeys=function(e,t,n){var r=void 0===n?{}:n,i=r.event,o=void 0===i?"keydown":i,a=r.capture,u=c(t,{timeout:r.timeout});return e.addEventListener(o,u,a),function(){e.removeEventListener(o,u,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\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
+ {"version":3,"file":"tinykeys.js","sources":["../src/tinykeys.ts"],"sourcesContent":["/**\n * A single press of a keybinding sequence\n */\nexport type KeyBindingPress = [mods: string[], key: string | RegExp]\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 * Key presses will use a capture listener (default: false)\n\t */\n\tcapture?: boolean\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\" as const\n\n/**\n * Platform detection code.\n * @see https://github.com/jamiebuilds/tinykeys/issues/184\n */\nlet PLATFORM = typeof navigator === \"object\" ? navigator.platform : \"\"\nlet APPLE_DEVICE = /Mac|iPod|iPhone|iPad/.test(PLATFORM)\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD = APPLE_DEVICE ? \"Meta\" : \"Control\"\n\n/**\n * Meaning of `AltGraph`, from MDN:\n * - Windows: Both Alt and Ctrl keys are pressed, or AltGr key is pressed\n * - Mac: ⌥ Option key pressed\n * - Linux: Level 3 Shift key (or Level 5 Shift key) pressed\n * - Android: Not supported\n * @see https://github.com/jamiebuilds/tinykeys/issues/185\n */\nlet ALT_GRAPH_ALIASES =\n\tPLATFORM === \"Win32\" ? [\"Control\", \"Alt\"] : APPLE_DEVICE ? [\"Alt\"] : []\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\t\t(ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState(\"AltGraph\"))\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 * <key> = `<KeyboardEvent.key>` or `<KeyboardEvent.code>` (case-insensitive)\n * <key> = `(<regex>)` -> `/^<regex>$/` (case-sensitive)\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: string | RegExp = mods.pop() as string\n\t\t\tlet match = key.match(/^\\((.+)\\)$/)\n\t\t\tif (match) {\n\t\t\t\tkey = new RegExp(`^${match[1]}$`)\n\t\t\t}\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 single keyboard event matches a single keybinding press.\n */\nexport function matchKeyBindingPress(\n\tevent: KeyboardEvent,\n\t[mods, key]: KeyBindingPress,\n): 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\tkey instanceof RegExp ? !(key.test(event.key) || key.test(event.code)) :\n\t\t\t(key.toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tkey !== event.code)\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tmods.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 !mods.includes(mod) && key !== 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 = matchKeyBindingPress(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\t{ event = DEFAULT_EVENT, capture, timeout }: KeyBindingOptions = {},\n): () => void {\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, { timeout })\n\ttarget.addEventListener(event, onKeyEvent, capture)\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent, capture)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","PLATFORM","navigator","platform","APPLE_DEVICE","test","MOD","ALT_GRAPH_ALIASES","getModifierState","event","mod","includes","parseKeybinding","str","trim","split","map","press","mods","key","pop","match","RegExp","matchKeyBindingPress","_ref","code","toUpperCase","find","createKeybindingsHandler","keyBindingMap","options","timeout","_options$timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","length","set","slice","clearTimeout","setTimeout","clear","bind","target","_temp","_ref2$event","_ref2","capture","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AA2CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAiBpDC,EAAgC,iBAAdC,UAAyBA,UAAUC,SAAW,GAChEC,EAAe,uBAAuBC,KAAKJ,GAK3CK,EAAMF,EAAe,OAAS,UAU9BG,EACU,UAAbN,EAAuB,CAAC,UAAW,OAASG,EAAe,CAAC,OAAS,GAMtE,SAASI,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,mBACjBC,EAAMD,iBAAiBE,IACtBH,EAAkBI,SAASD,IAAQD,EAAMD,iBAAiB,sBAc/CI,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAI,SAAAC,GACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAuBD,EAAKE,MAC5BC,EAAQF,EAAIE,MAAM,cAKtB,OAJIA,IACHF,EAAM,IAAIG,WAAWD,EAAM,SAGrB,CADPH,EAAOA,EAAKF,IAAI,SAAAN,SAAgB,SAARA,EAAiBJ,EAAMI,IACjCS,cAODI,EACfd,EAAoBe,OACnBN,EAAIM,KAAEL,EAAGK,KAGV,SAKEL,aAAeG,QAAWH,EAAId,KAAKI,EAAMU,OAAQA,EAAId,KAAKI,EAAMgB,MAC/DN,EAAIO,gBAAkBjB,EAAMU,IAAIO,eACjCP,IAAQV,EAAMgB,OAIfP,EAAKS,KAAK,SAAAjB,GACT,OAAQF,EAAiBC,EAAOC,MAMjCV,EAAyB2B,KAAK,SAAAjB,GAC7B,OAAQQ,EAAKP,SAASD,IAAQS,IAAQT,GAAOF,EAAiBC,EAAOC,eA2BxDkB,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,SAAOC,EAAGF,EAAQC,SAAOC,EA7HR,IA+HjBC,EAAcC,OAAOC,KAAKN,GAAeb,IAAI,SAAAG,GAChD,MAAO,CAACP,EAAgBO,GAAMU,EAAcV,MAGzCiB,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAO7B,GAIAA,aAAiB8B,gBAIvBN,EAAYO,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,EAG/BnB,EAAqBd,EAFRmC,EAAyB,IAazCA,EAAyBE,OAAS,EAC5CV,EAAgBW,IAAIL,EAAUE,EAAyBI,MAAM,KAE7DZ,SAAuBM,GACvBC,EAASlC,IAPJD,EAAiBC,EAAOA,EAAMU,MAClCiB,SAAuBM,KAUtBJ,GACHW,aAAaX,GAGdA,EAAQY,WAAWd,EAAgBe,MAAMC,KAAKhB,GAAkBL,2HA2BjEsB,EACAxB,EAA4ByB,oBACqC,GAAEA,EAAAC,EAAAC,EAAjE/C,MAAAA,WAAK8C,EArMY,UAqMIA,EAAEE,EAAOD,EAAPC,QAErBC,EAAa9B,EAAyBC,EAAe,CAAEE,QAFlByB,EAAPzB,UAIlC,OADAsB,EAAOM,iBAAiBlD,EAAOiD,EAAYD,cAE1CJ,EAAOO,oBAAoBnD,EAAOiD,EAAYD"}
@@ -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(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};
1
+ let e=["Shift","Meta","Alt","Control"],t="object"==typeof navigator?navigator.platform:"",n=/Mac|iPod|iPhone|iPad/.test(t),o=n?"Meta":"Control",i="Win32"===t?["Control","Alt"]:n?["Alt"]:[];function r(e,t){return"function"==typeof e.getModifierState&&(e.getModifierState(t)||i.includes(t)&&e.getModifierState("AltGraph"))}function a(e){return e.trim().split(" ").map(e=>{let t=e.split(/\b\+/),n=t.pop(),i=n.match(/^\((.+)\)$/);return i&&(n=new RegExp(`^${i[1]}$`)),t=t.map(e=>"$mod"===e?o:e),[t,n]})}function l(t,[n,o]){return!((o instanceof RegExp?!o.test(t.key)&&!o.test(t.code):o.toUpperCase()!==t.key.toUpperCase()&&o!==t.code)||n.find(e=>!r(t,e))||e.find(e=>!n.includes(e)&&o!==e&&r(t,e)))}function u(e,t={}){var n;let o=null!=(n=t.timeout)?n:1e3,i=Object.keys(e).map(t=>[a(t),e[t]]),u=new Map,c=null;return e=>{e instanceof KeyboardEvent&&(i.forEach(t=>{let n=t[0],o=t[1],i=u.get(n)||n;l(e,i[0])?i.length>1?u.set(n,i.slice(1)):(u.delete(n),o(e)):r(e,e.key)||u.delete(n)}),c&&clearTimeout(c),c=setTimeout(u.clear.bind(u),o))}}function c(e,t,{event:n="keydown",capture:o,timeout:i}={}){let r=u(t,{timeout:i});return e.addEventListener(n,r,o),()=>{e.removeEventListener(n,r,o)}}export{u as createKeybindingsHandler,l as matchKeyBindingPress,a as parseKeybinding,c 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\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
+ {"version":3,"file":"tinykeys.modern.js","sources":["../src/tinykeys.ts"],"sourcesContent":["/**\n * A single press of a keybinding sequence\n */\nexport type KeyBindingPress = [mods: string[], key: string | RegExp]\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 * Key presses will use a capture listener (default: false)\n\t */\n\tcapture?: boolean\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\" as const\n\n/**\n * Platform detection code.\n * @see https://github.com/jamiebuilds/tinykeys/issues/184\n */\nlet PLATFORM = typeof navigator === \"object\" ? navigator.platform : \"\"\nlet APPLE_DEVICE = /Mac|iPod|iPhone|iPad/.test(PLATFORM)\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD = APPLE_DEVICE ? \"Meta\" : \"Control\"\n\n/**\n * Meaning of `AltGraph`, from MDN:\n * - Windows: Both Alt and Ctrl keys are pressed, or AltGr key is pressed\n * - Mac: ⌥ Option key pressed\n * - Linux: Level 3 Shift key (or Level 5 Shift key) pressed\n * - Android: Not supported\n * @see https://github.com/jamiebuilds/tinykeys/issues/185\n */\nlet ALT_GRAPH_ALIASES =\n\tPLATFORM === \"Win32\" ? [\"Control\", \"Alt\"] : APPLE_DEVICE ? [\"Alt\"] : []\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\t\t(ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState(\"AltGraph\"))\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 * <key> = `<KeyboardEvent.key>` or `<KeyboardEvent.code>` (case-insensitive)\n * <key> = `(<regex>)` -> `/^<regex>$/` (case-sensitive)\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: string | RegExp = mods.pop() as string\n\t\t\tlet match = key.match(/^\\((.+)\\)$/)\n\t\t\tif (match) {\n\t\t\t\tkey = new RegExp(`^${match[1]}$`)\n\t\t\t}\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 single keyboard event matches a single keybinding press.\n */\nexport function matchKeyBindingPress(\n\tevent: KeyboardEvent,\n\t[mods, key]: KeyBindingPress,\n): 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\tkey instanceof RegExp ? !(key.test(event.key) || key.test(event.code)) :\n\t\t\t(key.toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tkey !== event.code)\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tmods.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 !mods.includes(mod) && key !== 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 = matchKeyBindingPress(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\t{ event = DEFAULT_EVENT, capture, timeout }: KeyBindingOptions = {},\n): () => void {\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, { timeout })\n\ttarget.addEventListener(event, onKeyEvent, capture)\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent, capture)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","PLATFORM","navigator","platform","APPLE_DEVICE","test","MOD","ALT_GRAPH_ALIASES","getModifierState","event","mod","includes","parseKeybinding","str","trim","split","map","press","mods","key","pop","match","RegExp","matchKeyBindingPress","code","toUpperCase","find","createKeybindingsHandler","keyBindingMap","options","timeout","_options$timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","length","set","slice","delete","clearTimeout","setTimeout","clear","bind","tinykeys","target","capture","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AA2CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAiBpDC,EAAgC,iBAAdC,UAAyBA,UAAUC,SAAW,GAChEC,EAAe,uBAAuBC,KAAKJ,GAK3CK,EAAMF,EAAe,OAAS,UAU9BG,EACU,UAAbN,EAAuB,CAAC,UAAW,OAASG,EAAe,CAAC,OAAS,GAMtE,SAASI,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,mBACjBC,EAAMD,iBAAiBE,IACtBH,EAAkBI,SAASD,IAAQD,EAAMD,iBAAiB,sBAc/CI,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAIC,IACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAuBD,EAAKE,MAC5BC,EAAQF,EAAIE,MAAM,cAKtB,OAJIA,IACHF,EAAM,IAAIG,OAAO,IAAID,EAAM,QAE5BH,EAAOA,EAAKF,IAAIN,GAAgB,SAARA,EAAiBJ,EAAMI,GACxC,CAACQ,EAAMC,cAODI,EACfd,GACCS,EAAMC,IAGP,SAKEA,aAAeG,QAAWH,EAAId,KAAKI,EAAMU,OAAQA,EAAId,KAAKI,EAAMe,MAC/DL,EAAIM,gBAAkBhB,EAAMU,IAAIM,eACjCN,IAAQV,EAAMe,OAIfN,EAAKQ,KAAKhB,IACDF,EAAiBC,EAAOC,KAMjCV,EAAyB0B,KAAKhB,IACrBQ,EAAKP,SAASD,IAAQS,IAAQT,GAAOF,EAAiBC,EAAOC,cA2BxDiB,EACfC,EACAC,EAAoC,UAEpC,IAAIC,SAAOC,EAAGF,EAAQC,SAAOC,EA7HR,IA+HjBC,EAAcC,OAAOC,KAAKN,GAAeZ,IAAIG,GACzC,CAACP,EAAgBO,GAAMS,EAAcT,KAGzCgB,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,OAAO5B,IAIAA,aAAiB6B,gBAIvBN,EAAYO,QAAQC,IACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,EAG/BlB,EAAqBd,EAFRkC,EAAyB,IAazCA,EAAyBE,OAAS,EAC5CV,EAAgBW,IAAIL,EAAUE,EAAyBI,MAAM,KAE7DZ,EAAgBa,OAAOP,GACvBC,EAASjC,IAPJD,EAAiBC,EAAOA,EAAMU,MAClCgB,EAAgBa,OAAOP,KAUtBJ,GACHY,aAAaZ,GAGdA,EAAQa,WAAWf,EAAgBgB,MAAMC,KAAKjB,GAAkBL,cA0BlDuB,EACfC,EACA1B,GACAnB,MAAEA,EArMiB,UAqMI8C,QAAEA,EAAOzB,QAAEA,GAA+B,IAEjE,IAAI0B,EAAa7B,EAAyBC,EAAe,CAAEE,QAAAA,IAE3D,OADAwB,EAAOG,iBAAiBhD,EAAO+C,EAAYD,GACpC,KACND,EAAOI,oBAAoBjD,EAAO+C,EAAYD"}
@@ -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(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};
1
+ var t=["Shift","Meta","Alt","Control"],e="object"==typeof navigator?navigator.platform:"",n=/Mac|iPod|iPhone|iPad/.test(e),o=n?"Meta":"Control",r="Win32"===e?["Control","Alt"]:n?["Alt"]:[];function i(t,e){return"function"==typeof t.getModifierState&&(t.getModifierState(e)||r.includes(e)&&t.getModifierState("AltGraph"))}function a(t){return t.trim().split(" ").map(function(t){var e=t.split(/\b\+/),n=e.pop(),r=n.match(/^\((.+)\)$/);return r&&(n=new RegExp("^"+r[1]+"$")),[e=e.map(function(t){return"$mod"===t?o:t}),n]})}function u(e,n){var o=n[0],r=n[1];return!((r instanceof RegExp?!r.test(e.key)&&!r.test(e.code):r.toUpperCase()!==e.key.toUpperCase()&&r!==e.code)||o.find(function(t){return!i(e,t)})||t.find(function(t){return!o.includes(t)&&r!==t&&i(e,t)}))}function c(t,e){var n;void 0===e&&(e={});var o=null!=(n=e.timeout)?n:1e3,r=Object.keys(t).map(function(e){return[a(e),t[e]]}),c=new Map,f=null;return function(t){t instanceof KeyboardEvent&&(r.forEach(function(e){var n=e[0],o=e[1],r=c.get(n)||n;u(t,r[0])?r.length>1?c.set(n,r.slice(1)):(c.delete(n),o(t)):i(t,t.key)||c.delete(n)}),f&&clearTimeout(f),f=setTimeout(c.clear.bind(c),o))}}function f(t,e,n){var o=void 0===n?{}:n,r=o.event,i=void 0===r?"keydown":r,a=o.capture,u=c(e,{timeout:o.timeout});return t.addEventListener(i,u,a),function(){t.removeEventListener(i,u,a)}}export{c as createKeybindingsHandler,u as matchKeyBindingPress,a as parseKeybinding,f 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\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
+ {"version":3,"file":"tinykeys.module.js","sources":["../src/tinykeys.ts"],"sourcesContent":["/**\n * A single press of a keybinding sequence\n */\nexport type KeyBindingPress = [mods: string[], key: string | RegExp]\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 * Key presses will use a capture listener (default: false)\n\t */\n\tcapture?: boolean\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\" as const\n\n/**\n * Platform detection code.\n * @see https://github.com/jamiebuilds/tinykeys/issues/184\n */\nlet PLATFORM = typeof navigator === \"object\" ? navigator.platform : \"\"\nlet APPLE_DEVICE = /Mac|iPod|iPhone|iPad/.test(PLATFORM)\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD = APPLE_DEVICE ? \"Meta\" : \"Control\"\n\n/**\n * Meaning of `AltGraph`, from MDN:\n * - Windows: Both Alt and Ctrl keys are pressed, or AltGr key is pressed\n * - Mac: ⌥ Option key pressed\n * - Linux: Level 3 Shift key (or Level 5 Shift key) pressed\n * - Android: Not supported\n * @see https://github.com/jamiebuilds/tinykeys/issues/185\n */\nlet ALT_GRAPH_ALIASES =\n\tPLATFORM === \"Win32\" ? [\"Control\", \"Alt\"] : APPLE_DEVICE ? [\"Alt\"] : []\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\t\t(ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState(\"AltGraph\"))\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 * <key> = `<KeyboardEvent.key>` or `<KeyboardEvent.code>` (case-insensitive)\n * <key> = `(<regex>)` -> `/^<regex>$/` (case-sensitive)\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: string | RegExp = mods.pop() as string\n\t\t\tlet match = key.match(/^\\((.+)\\)$/)\n\t\t\tif (match) {\n\t\t\t\tkey = new RegExp(`^${match[1]}$`)\n\t\t\t}\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 single keyboard event matches a single keybinding press.\n */\nexport function matchKeyBindingPress(\n\tevent: KeyboardEvent,\n\t[mods, key]: KeyBindingPress,\n): 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\tkey instanceof RegExp ? !(key.test(event.key) || key.test(event.code)) :\n\t\t\t(key.toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tkey !== event.code)\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tmods.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 !mods.includes(mod) && key !== 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 = matchKeyBindingPress(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\t{ event = DEFAULT_EVENT, capture, timeout }: KeyBindingOptions = {},\n): () => void {\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, { timeout })\n\ttarget.addEventListener(event, onKeyEvent, capture)\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent, capture)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","PLATFORM","navigator","platform","APPLE_DEVICE","test","MOD","ALT_GRAPH_ALIASES","getModifierState","event","mod","includes","parseKeybinding","str","trim","split","map","press","mods","key","pop","match","RegExp","matchKeyBindingPress","_ref","code","toUpperCase","find","createKeybindingsHandler","keyBindingMap","options","timeout","_options$timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","length","set","slice","clearTimeout","setTimeout","clear","bind","tinykeys","target","_temp","_ref2$event","_ref2","capture","onKeyEvent","addEventListener","removeEventListener"],"mappings":"AA2CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAiBpDC,EAAgC,iBAAdC,UAAyBA,UAAUC,SAAW,GAChEC,EAAe,uBAAuBC,KAAKJ,GAK3CK,EAAMF,EAAe,OAAS,UAU9BG,EACU,UAAbN,EAAuB,CAAC,UAAW,OAASG,EAAe,CAAC,OAAS,GAMtE,SAASI,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,mBACjBC,EAAMD,iBAAiBE,IACtBH,EAAkBI,SAASD,IAAQD,EAAMD,iBAAiB,sBAc/CI,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAI,SAAAC,GACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAuBD,EAAKE,MAC5BC,EAAQF,EAAIE,MAAM,cAKtB,OAJIA,IACHF,EAAM,IAAIG,WAAWD,EAAM,SAGrB,CADPH,EAAOA,EAAKF,IAAI,SAAAN,SAAgB,SAARA,EAAiBJ,EAAMI,IACjCS,cAODI,EACfd,EAAoBe,OACnBN,EAAIM,KAAEL,EAAGK,KAGV,SAKEL,aAAeG,QAAWH,EAAId,KAAKI,EAAMU,OAAQA,EAAId,KAAKI,EAAMgB,MAC/DN,EAAIO,gBAAkBjB,EAAMU,IAAIO,eACjCP,IAAQV,EAAMgB,OAIfP,EAAKS,KAAK,SAAAjB,GACT,OAAQF,EAAiBC,EAAOC,MAMjCV,EAAyB2B,KAAK,SAAAjB,GAC7B,OAAQQ,EAAKP,SAASD,IAAQS,IAAQT,GAAOF,EAAiBC,EAAOC,eA2BxDkB,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,SAAOC,EAAGF,EAAQC,SAAOC,EA7HR,IA+HjBC,EAAcC,OAAOC,KAAKN,GAAeb,IAAI,SAAAG,GAChD,MAAO,CAACP,EAAgBO,GAAMU,EAAcV,MAGzCiB,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAO7B,GAIAA,aAAiB8B,gBAIvBN,EAAYO,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,EAG/BnB,EAAqBd,EAFRmC,EAAyB,IAazCA,EAAyBE,OAAS,EAC5CV,EAAgBW,IAAIL,EAAUE,EAAyBI,MAAM,KAE7DZ,SAAuBM,GACvBC,EAASlC,IAPJD,EAAiBC,EAAOA,EAAMU,MAClCiB,SAAuBM,KAUtBJ,GACHW,aAAaX,GAGdA,EAAQY,WAAWd,EAAgBe,MAAMC,KAAKhB,GAAkBL,cA0BlDsB,EACfC,EACAzB,EAA4B0B,oBACqC,GAAEA,EAAAC,EAAAC,EAAjEhD,MAAAA,WAAK+C,EArMY,UAqMIA,EAAEE,EAAOD,EAAPC,QAErBC,EAAa/B,EAAyBC,EAAe,CAAEE,QAFlB0B,EAAP1B,UAIlC,OADAuB,EAAOM,iBAAiBnD,EAAOkD,EAAYD,cAE1CJ,EAAOO,oBAAoBpD,EAAOkD,EAAYD"}
@@ -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 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)}}});
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?navigator.platform:"",i=/Mac|iPod|iPhone|iPad/.test(n),o=i?"Meta":"Control",r="Win32"===n?["Control","Alt"]:i?["Alt"]:[];function a(e,t){return"function"==typeof e.getModifierState&&(e.getModifierState(t)||r.includes(t)&&e.getModifierState("AltGraph"))}function u(e){return e.trim().split(" ").map(function(e){var t=e.split(/\b\+/),n=t.pop(),i=n.match(/^\((.+)\)$/);return i&&(n=new RegExp("^"+i[1]+"$")),[t=t.map(function(e){return"$mod"===e?o:e}),n]})}function f(e,n){var i=n[0],o=n[1];return!((o instanceof RegExp?!o.test(e.key)&&!o.test(e.code):o.toUpperCase()!==e.key.toUpperCase()&&o!==e.code)||i.find(function(t){return!a(e,t)})||t.find(function(t){return!i.includes(t)&&o!==t&&a(e,t)}))}function c(e,t){var n;void 0===t&&(t={});var i=null!=(n=t.timeout)?n:1e3,o=Object.keys(e).map(function(t){return[u(t),e[t]]}),r=new Map,c=null;return function(e){e instanceof KeyboardEvent&&(o.forEach(function(t){var n=t[0],i=t[1],o=r.get(n)||n;f(e,o[0])?o.length>1?r.set(n,o.slice(1)):(r.delete(n),i(e)):a(e,e.key)||r.delete(n)}),c&&clearTimeout(c),c=setTimeout(r.clear.bind(r),i))}}e.createKeybindingsHandler=c,e.matchKeyBindingPress=f,e.parseKeybinding=u,e.tinykeys=function(e,t,n){var i=void 0===n?{}:n,o=i.event,r=void 0===o?"keydown":o,a=i.capture,u=c(t,{timeout:i.timeout});return e.addEventListener(r,u,a),function(){e.removeEventListener(r,u,a)}}});
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\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"}
1
+ {"version":3,"file":"tinykeys.umd.js","sources":["../src/tinykeys.ts"],"sourcesContent":["/**\n * A single press of a keybinding sequence\n */\nexport type KeyBindingPress = [mods: string[], key: string | RegExp]\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 * Key presses will use a capture listener (default: false)\n\t */\n\tcapture?: boolean\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\" as const\n\n/**\n * Platform detection code.\n * @see https://github.com/jamiebuilds/tinykeys/issues/184\n */\nlet PLATFORM = typeof navigator === \"object\" ? navigator.platform : \"\"\nlet APPLE_DEVICE = /Mac|iPod|iPhone|iPad/.test(PLATFORM)\n\n/**\n * An alias for creating platform-specific keybinding aliases.\n */\nlet MOD = APPLE_DEVICE ? \"Meta\" : \"Control\"\n\n/**\n * Meaning of `AltGraph`, from MDN:\n * - Windows: Both Alt and Ctrl keys are pressed, or AltGr key is pressed\n * - Mac: ⌥ Option key pressed\n * - Linux: Level 3 Shift key (or Level 5 Shift key) pressed\n * - Android: Not supported\n * @see https://github.com/jamiebuilds/tinykeys/issues/185\n */\nlet ALT_GRAPH_ALIASES =\n\tPLATFORM === \"Win32\" ? [\"Control\", \"Alt\"] : APPLE_DEVICE ? [\"Alt\"] : []\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\t\t(ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState(\"AltGraph\"))\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 * <key> = `<KeyboardEvent.key>` or `<KeyboardEvent.code>` (case-insensitive)\n * <key> = `(<regex>)` -> `/^<regex>$/` (case-sensitive)\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: string | RegExp = mods.pop() as string\n\t\t\tlet match = key.match(/^\\((.+)\\)$/)\n\t\t\tif (match) {\n\t\t\t\tkey = new RegExp(`^${match[1]}$`)\n\t\t\t}\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 single keyboard event matches a single keybinding press.\n */\nexport function matchKeyBindingPress(\n\tevent: KeyboardEvent,\n\t[mods, key]: KeyBindingPress,\n): 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\tkey instanceof RegExp ? !(key.test(event.key) || key.test(event.code)) :\n\t\t\t(key.toUpperCase() !== event.key.toUpperCase() &&\n\t\t\tkey !== event.code)\n\t\t) ||\n\n\t\t// Ensure all the modifiers in the keybinding are pressed.\n\t\tmods.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 !mods.includes(mod) && key !== 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 = matchKeyBindingPress(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\t{ event = DEFAULT_EVENT, capture, timeout }: KeyBindingOptions = {},\n): () => void {\n\tlet onKeyEvent = createKeybindingsHandler(keyBindingMap, { timeout })\n\ttarget.addEventListener(event, onKeyEvent, capture)\n\treturn () => {\n\t\ttarget.removeEventListener(event, onKeyEvent, capture)\n\t}\n}\n"],"names":["KEYBINDING_MODIFIER_KEYS","PLATFORM","navigator","platform","APPLE_DEVICE","test","MOD","ALT_GRAPH_ALIASES","getModifierState","event","mod","includes","parseKeybinding","str","trim","split","map","press","mods","key","pop","match","RegExp","matchKeyBindingPress","_ref","code","toUpperCase","find","createKeybindingsHandler","keyBindingMap","options","timeout","_options$timeout","keyBindings","Object","keys","possibleMatches","Map","timer","KeyboardEvent","forEach","keyBinding","sequence","callback","remainingExpectedPresses","get","length","set","slice","clearTimeout","setTimeout","clear","bind","target","_temp","_ref2$event","_ref2","capture","onKeyEvent","addEventListener","removeEventListener"],"mappings":"kOA2CA,IAAIA,EAA2B,CAAC,QAAS,OAAQ,MAAO,WAiBpDC,EAAgC,iBAAdC,UAAyBA,UAAUC,SAAW,GAChEC,EAAe,uBAAuBC,KAAKJ,GAK3CK,EAAMF,EAAe,OAAS,UAU9BG,EACU,UAAbN,EAAuB,CAAC,UAAW,OAASG,EAAe,CAAC,OAAS,GAMtE,SAASI,EAAiBC,EAAsBC,GAC/C,MAAyC,mBAA3BD,EAAMD,mBACjBC,EAAMD,iBAAiBE,IACtBH,EAAkBI,SAASD,IAAQD,EAAMD,iBAAiB,sBAc/CI,EAAgBC,GAC/B,OAAOA,EACLC,OACAC,MAAM,KACNC,IAAI,SAAAC,GACJ,IAAIC,EAAOD,EAAMF,MAAM,QACnBI,EAAuBD,EAAKE,MAC5BC,EAAQF,EAAIE,MAAM,cAKtB,OAJIA,IACHF,EAAM,IAAIG,WAAWD,EAAM,SAGrB,CADPH,EAAOA,EAAKF,IAAI,SAAAN,SAAgB,SAARA,EAAiBJ,EAAMI,IACjCS,cAODI,EACfd,EAAoBe,OACnBN,EAAIM,KAAEL,EAAGK,KAGV,SAKEL,aAAeG,QAAWH,EAAId,KAAKI,EAAMU,OAAQA,EAAId,KAAKI,EAAMgB,MAC/DN,EAAIO,gBAAkBjB,EAAMU,IAAIO,eACjCP,IAAQV,EAAMgB,OAIfP,EAAKS,KAAK,SAAAjB,GACT,OAAQF,EAAiBC,EAAOC,MAMjCV,EAAyB2B,KAAK,SAAAjB,GAC7B,OAAQQ,EAAKP,SAASD,IAAQS,IAAQT,GAAOF,EAAiBC,EAAOC,eA2BxDkB,EACfC,EACAC,kBAAAA,IAAAA,EAAoC,IAEpC,IAAIC,SAAOC,EAAGF,EAAQC,SAAOC,EA7HR,IA+HjBC,EAAcC,OAAOC,KAAKN,GAAeb,IAAI,SAAAG,GAChD,MAAO,CAACP,EAAgBO,GAAMU,EAAcV,MAGzCiB,EAAkB,IAAIC,IACtBC,EAAuB,KAE3B,gBAAO7B,GAIAA,aAAiB8B,gBAIvBN,EAAYO,QAAQ,SAAAC,GACnB,IAAIC,EAAWD,EAAW,GACtBE,EAAWF,EAAW,GAGtBG,EADOR,EAAgBS,IAAIH,IACcA,EAG/BnB,EAAqBd,EAFRmC,EAAyB,IAazCA,EAAyBE,OAAS,EAC5CV,EAAgBW,IAAIL,EAAUE,EAAyBI,MAAM,KAE7DZ,SAAuBM,GACvBC,EAASlC,IAPJD,EAAiBC,EAAOA,EAAMU,MAClCiB,SAAuBM,KAUtBJ,GACHW,aAAaX,GAGdA,EAAQY,WAAWd,EAAgBe,MAAMC,KAAKhB,GAAkBL,mGA2BjEsB,EACAxB,EAA4ByB,oBACqC,GAAEA,EAAAC,EAAAC,EAAjE/C,MAAAA,WAAK8C,EArMY,UAqMIA,EAAEE,EAAOD,EAAPC,QAErBC,EAAa9B,EAAyBC,EAAe,CAAEE,QAFlByB,EAAPzB,UAIlC,OADAsB,EAAOM,iBAAiBlD,EAAOiD,EAAYD,cAE1CJ,EAAOO,oBAAoBnD,EAAOiD,EAAYD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tinykeys",
3
- "version": "2.0.0",
4
- "description": "A tiny (~400 B) & modern library for keybindings.",
3
+ "version": "3.0.0",
4
+ "description": "A tiny (~650 B) & modern library for keybindings.",
5
5
  "author": "Jamie Kyle <me@thejameskyle.com>",
6
6
  "license": "MIT",
7
7
  "repository": "jamiebuilds/tinykeys",
@@ -52,9 +52,11 @@
52
52
  "prepublishOnly": "npm run -s build"
53
53
  },
54
54
  "devDependencies": {
55
+ "@types/canvas-confetti": "^1.6.4",
55
56
  "@typescript-eslint/eslint-plugin": "^4.0.0",
56
57
  "@typescript-eslint/parser": "^3.7.1",
57
58
  "ava": "^3.11.0",
59
+ "canvas-confetti": "^1.9.3",
58
60
  "eslint": "^7.5.0",
59
61
  "eslint-plugin-ava": "^12.0.0",
60
62
  "husky": "^6.0.0",
@@ -80,6 +82,5 @@
80
82
  "require": [
81
83
  "ts-node/register"
82
84
  ]
83
- },
84
- "dependencies": {}
85
+ }
85
86
  }