@solid-primitives/keyboard 0.0.100 → 1.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
@@ -11,7 +11,11 @@
11
11
 
12
12
  A library of reactive promitives helping handling user's keyboard input.
13
13
 
14
- - [`makeKeyHoldListener`](#makeKeyHoldListener) - Attaches keyboard event-listeners, and triggers callback whenever user holds or stops holding specified key.
14
+ - [`useKeyDownList`](#useKeyDownList) Provides a signal with the list of currently held keys
15
+ - [`useCurrentlyHeldKey`](#useCurrentlyHeldKey) — Provides a signal with the currently held single key.
16
+ - [`useKeyDownSequence`](#useKeyDownSequence) — Provides a signal with a sequence of currently held keys, as they were pressed down and up.
17
+ - [`createKeyHold`](#createKeyHold) — Provides a signal indicating if provided key is currently being held down.
18
+ - [`createShortcut`](#createShortcut) — Creates a keyboard shotcut observer.
15
19
 
16
20
  ## Installation
17
21
 
@@ -21,34 +25,128 @@ npm install @solid-primitives/keyboard
21
25
  yarn add @solid-primitives/keyboard
22
26
  ```
23
27
 
24
- ## `makeKeyHoldListener`
28
+ ## `useKeyDownList`
25
29
 
26
- Attaches keyboard event-listeners to `window`, and calls provided callback whenever user holds or stops holding specified key.
30
+ Provides a signal with the list of currently held keys, ordered from least recent to most recent.
27
31
 
28
- Event listeners are automatically cleaned on root dispose.
32
+ This is a [shared root](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSharedRoot) primitive that will reuse event listeners and signals across dependents.
29
33
 
30
34
  ### How to use it
31
35
 
32
- `makeKeyHoldListener` takes three arguments:
36
+ `useKeyDownList` takes no arguments, and returns a signal with the list of currently held keys, and last keydown event.
33
37
 
34
- - `key` keyboard key or modifier to listen for
35
- - `onHoldChange` callback fired when the hold state changes
36
- - `options` additional configuration:
37
- - `preventDefault` call `e.preventDefault()` on the keyboard event, when the specified key is pressed. _(Defaults to `false`)_
38
- - `allowOtherKeys` — Should the user be allowed to press other keys while holding the specified one _(Defaults to `false`)_
38
+ ```tsx
39
+ import { useKeyDownList } from "@solid-primitives/keyboard";
40
+
41
+ const [keys, { event }] = useKeyDownList();
42
+
43
+ createEffect(() => {
44
+ console.log(keys()); // => string[] — list of currently held keys
45
+ console.log(event()); // => KeyboardEvent | null — last keydown event
46
+ });
47
+
48
+ <For each={keys()}>
49
+ {key => <kbd>{key}</kdb>}
50
+ </For>
51
+ ```
52
+
53
+ ## `useCurrentlyHeldKey`
54
+
55
+ Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`.
56
+
57
+ This is a [shared root](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSharedRoot) primitive that will reuse event listeners and signals across dependents.
58
+
59
+ ### How to use it
60
+
61
+ `useCurrentlyHeldKey` takes no arguments, and returns a signal with the currently held single key.
39
62
 
40
63
  ```tsx
41
- import { makeKeyHoldListener } from "@solid-primitives/keyboard";
64
+ import { useCurrentlyHeldKey } from "@solid-primitives/keyboard";
42
65
 
43
- const [pressing, setPressing] = createSignal(false);
66
+ const key = useCurrentlyHeldKey();
44
67
 
45
- makeKeyHoldListener("altKey", setPressing, {
46
- preventDefault: true
68
+ createEffect(() => {
69
+ console.log(key()); // => string | null — currently held key
47
70
  });
71
+ ```
72
+
73
+ ## `useKeyDownSequence`
74
+
75
+ Provides a signal with a sequence of currently held keys, as they were pressed down and up.
76
+
77
+ This is a [shared root](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSharedRoot) primitive that will reuse event listeners and signals across dependents.
78
+
79
+ ### How to use it
80
+
81
+ `useKeyDownSequence` takes no arguments, and returns a single signal.
82
+
83
+ ```tsx
84
+ import { useKeyDownSequence } from "@solid-primitives/keyboard";
85
+
86
+ const sequence = useKeyDownSequence();
87
+
88
+ createEffect(() => {
89
+ console.log(sequence()); // => string[][] — sequence of currently held keys
90
+ });
91
+
92
+ // example sequence of pressing Ctrl + Shift + A
93
+ // [["Control"], ["Control", "Shift"], ["Control", "Shift", "A"]]
94
+ ```
95
+
96
+ ## `createKeyHold`
97
+
98
+ Provides a `boolean` signal indicating if provided {@link key} is currently being held down.
99
+
100
+ Holding multiple keys at the same time will return `false` — holding only the specified one will return `true`.
101
+
102
+ ### How to use it
103
+
104
+ `createKeyHold` takes two arguments:
105
+
106
+ - `key` keyboard key to listen for
107
+ - `options` additional configuration:
108
+ - `preventDefault` — call `e.preventDefault()` on the keyboard event, when the specified key is pressed. _(Defaults to `true`)_
109
+
110
+ ```tsx
111
+ import { createKeyHold } from "@solid-primitives/keyboard";
112
+
113
+ const pressing = createKeyHold("Alt", { preventDefault: false });
48
114
 
49
115
  <p>Is pressing Alt? {pressing() ? "YES" : "NO"}</p>;
50
116
  ```
51
117
 
118
+ ## `createShortcut`
119
+
120
+ Creates a keyboard shotcut observer. The provided callback will be called when the specified keys are pressed.
121
+
122
+ ### How to use it
123
+
124
+ `createShortcut` takes three arguments:
125
+
126
+ - `keys` — list of keys to listen for
127
+ - `callback` — callback to call when the specified keys are pressed
128
+ - `options` — additional configuration:
129
+ - `preventDefault` — call `e.preventDefault()` on the keyboard event, when the specified key is pressed. _(Defaults to `true`)_
130
+ - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
131
+
132
+ ```tsx
133
+ import { createShortcut } from "@solid-primitives/keyboard";
134
+
135
+ createShortcut(
136
+ ["Control", "Shift", "A"],
137
+ () => {
138
+ console.log("Shortcut triggered");
139
+ },
140
+ { preventDefault: false, requireReset: true }
141
+ );
142
+ ```
143
+
144
+ ### Preventing default
145
+
146
+ When `preventDefault` is `true`, `e.preventDefault()` will be called not only on the keydown event that have triggered the callback, but it will **optimistically** also prevend the default behavior of every previous keydown that will have the possibility to lead to the shotcut being pressed.
147
+
148
+ E.g. when listening for `Control + Shift + A`, all three keydown events will be prevented.
149
+
52
150
  ## Changelog
53
151
 
54
152
  <details>
@@ -58,4 +156,16 @@ makeKeyHoldListener("altKey", setPressing, {
58
156
 
59
157
  Initial release as a Stage-0 primitive.
60
158
 
159
+ 1.0.0
160
+
161
+ [PR#159](https://github.com/solidjs-community/solid-primitives/pull/159)
162
+
163
+ General package refactor. The single initial `makeKeyHoldListener` primitive has been replaced by:
164
+
165
+ - `useKeyDownList`,
166
+ - `useCurrentlyHeldKey`,
167
+ - `useKeyDownSequence`,
168
+ - `createKeyHold`,
169
+ - `createShortcut`
170
+
61
171
  </details>
package/dist/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -19,55 +20,135 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
20
  // src/index.ts
20
21
  var src_exports = {};
21
22
  __export(src_exports, {
22
- makeKeyHoldListener: () => makeKeyHoldListener
23
+ createKeyHold: () => createKeyHold,
24
+ createShortcut: () => createShortcut,
25
+ useCurrentlyHeldKey: () => useCurrentlyHeldKey,
26
+ useKeyDownList: () => useKeyDownList,
27
+ useKeyDownSequence: () => useKeyDownSequence
23
28
  });
24
29
  module.exports = __toCommonJS(src_exports);
25
-
26
- // src/holdKeyListener.ts
27
30
  var import_event_listener = require("@solid-primitives/event-listener");
28
- var keyModifiers = ["altKey", "ctrlKey", "metaKey", "shiftKey"];
29
- function makeKeyHoldListener(key, onHoldChange, options = {}) {
30
- const { preventDefault, allowOtherKeys } = options;
31
- const modifier = keyModifiers.includes(key) ? key : void 0;
32
- let state = false;
33
- let actualPressed = false;
34
- const updateState = (newState) => {
35
- newState !== state && onHoldChange(state = newState);
36
- };
37
- (0, import_event_listener.makeEventListener)(window, "keydown", modifier ? (e) => {
31
+ var import_rootless = require("@solid-primitives/rootless");
32
+ var import_utils = require("@solid-primitives/utils");
33
+ var import_solid_js = require("solid-js");
34
+ function equalsKeyHoldSequence(sequence, model) {
35
+ for (let i = sequence.length - 1; i >= 0; i--) {
36
+ const _model = model.slice(0, i + 1);
37
+ if (!(0, import_utils.arrayEquals)(sequence[i], _model))
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+ var useKeyDownList = /* @__PURE__ */ (0, import_rootless.createSharedRoot)(() => {
43
+ const [pressedKeys, setPressedKeys] = (0, import_solid_js.createSignal)([]);
44
+ const [event, setEvent] = (0, import_solid_js.createSignal)(null);
45
+ const reset = () => setPressedKeys([]);
46
+ (0, import_event_listener.makeEventListener)(window, "keydown", (e) => {
38
47
  if (e.repeat)
39
48
  return;
40
- if (e[modifier]) {
41
- if (!state && !actualPressed) {
42
- preventDefault && e.preventDefault();
43
- onHoldChange(state = actualPressed = true);
44
- } else if (!allowOtherKeys)
45
- updateState(false);
46
- } else
47
- updateState(actualPressed = false);
48
- } : (e) => {
49
- if (e.key !== key) {
50
- allowOtherKeys || updateState(false);
51
- return;
52
- }
53
- if (e.repeat)
49
+ const key = e.key.toUpperCase();
50
+ if (pressedKeys().includes(key))
54
51
  return;
55
- updateState(true);
52
+ (0, import_solid_js.batch)(() => {
53
+ setEvent(e);
54
+ setPressedKeys((prev) => [...prev, key]);
55
+ });
56
+ });
57
+ (0, import_event_listener.makeEventListener)(window, "keyup", (e) => {
58
+ const key = e.key.toUpperCase();
59
+ setPressedKeys((prev) => prev.filter((_key) => _key !== key));
60
+ });
61
+ (0, import_event_listener.makeEventListener)(window, "blur", reset);
62
+ (0, import_event_listener.makeEventListener)(window, "contextmenu", (e) => {
63
+ e.defaultPrevented || reset();
56
64
  });
57
- (0, import_event_listener.makeEventListener)(window, "keyup", modifier ? (e) => {
58
- if (e[modifier])
59
- allowOtherKeys || updateState(false);
60
- else
61
- updateState(actualPressed = false);
62
- } : (e) => {
63
- if (e.key !== key)
64
- allowOtherKeys || updateState(false);
65
- else
66
- updateState(false);
65
+ return [pressedKeys, { event }];
66
+ });
67
+ var useCurrentlyHeldKey = /* @__PURE__ */ (0, import_rootless.createSharedRoot)(() => {
68
+ const [keys] = useKeyDownList();
69
+ let prevKeys = (0, import_solid_js.untrack)(keys);
70
+ return (0, import_solid_js.createMemo)(() => {
71
+ const _keys = keys();
72
+ const prev = prevKeys;
73
+ prevKeys = _keys;
74
+ if (prev.length === 0 && _keys.length === 1)
75
+ return _keys[0];
76
+ return null;
67
77
  });
68
- (0, import_event_listener.makeEventListener)(document, "visibilitychange", () => document.visibilityState !== "visible" && updateState(false));
78
+ });
79
+ var useKeyDownSequence = /* @__PURE__ */ (0, import_rootless.createSharedRoot)(() => {
80
+ const [keys] = useKeyDownList();
81
+ return (0, import_solid_js.createMemo)((prev) => {
82
+ if (keys().length === 0)
83
+ return [];
84
+ return [...prev, keys()];
85
+ }, []);
86
+ });
87
+ function createKeyHold(key, options = {}) {
88
+ key = key.toUpperCase();
89
+ const { preventDefault = true } = options;
90
+ const [, { event }] = useKeyDownList();
91
+ const heldKey = useCurrentlyHeldKey();
92
+ return (0, import_solid_js.createMemo)(() => {
93
+ if (heldKey() === key) {
94
+ preventDefault && event().preventDefault();
95
+ return true;
96
+ }
97
+ return false;
98
+ });
99
+ }
100
+ function createShortcut(keys, callback, options = {}) {
101
+ if (!keys.length)
102
+ return;
103
+ keys = keys.map((key) => key.toUpperCase());
104
+ const { preventDefault = true, requireReset = false } = options;
105
+ const [, { event }] = useKeyDownList();
106
+ const sequence = useKeyDownSequence();
107
+ let reset = false;
108
+ const handleSequenceWithReset = (sequence2) => {
109
+ if (!sequence2.length)
110
+ return reset = false;
111
+ if (reset)
112
+ return;
113
+ if (sequence2.length < keys.length) {
114
+ if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
115
+ preventDefault && event().preventDefault();
116
+ } else {
117
+ reset = true;
118
+ }
119
+ } else {
120
+ reset = true;
121
+ if (equalsKeyHoldSequence(sequence2, keys)) {
122
+ preventDefault && event().preventDefault();
123
+ callback();
124
+ }
125
+ }
126
+ };
127
+ const handleSequenceWithoutReset = (sequence2) => {
128
+ const last = sequence2.at(-1);
129
+ if (!last)
130
+ return;
131
+ if (preventDefault && last.length < keys.length) {
132
+ if ((0, import_utils.arrayEquals)(last, keys.slice(0, keys.length - 1))) {
133
+ event().preventDefault();
134
+ }
135
+ return;
136
+ }
137
+ if ((0, import_utils.arrayEquals)(last, keys)) {
138
+ const prev = sequence2.at(-2);
139
+ if (!prev || (0, import_utils.arrayEquals)(prev, keys.slice(0, keys.length - 1))) {
140
+ preventDefault && event().preventDefault();
141
+ callback();
142
+ }
143
+ }
144
+ };
145
+ (0, import_solid_js.createEffect)((0, import_solid_js.on)(sequence, requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
69
146
  }
70
147
  // Annotate the CommonJS export names for ESM import in node:
71
148
  0 && (module.exports = {
72
- makeKeyHoldListener
149
+ createKeyHold,
150
+ createShortcut,
151
+ useCurrentlyHeldKey,
152
+ useKeyDownList,
153
+ useKeyDownSequence
73
154
  });
package/dist/index.d.ts CHANGED
@@ -1,19 +1,120 @@
1
- declare type KeyModifier = "altKey" | "ctrlKey" | "metaKey" | "shiftKey";
2
- declare type KeyToHold = KeyModifier | (string & {});
1
+ import { Accessor } from 'solid-js';
2
+
3
+ declare type ModifierKey = "Alt" | "Control" | "Meta" | "Shift";
4
+ declare type KbdKey = ModifierKey | (string & {});
5
+ /**
6
+ * Provides a signal with the list of currently held keys, ordered from least recent to most recent.
7
+ *
8
+ * This is a [shared root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSharedRoot). *(signals and event-listeners are reused across dependents)*
9
+ *
10
+ * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownList
11
+ *
12
+ * @returns
13
+ * Returns a signal of a list of keys, and a signal of last keydown event.
14
+ * ```ts
15
+ * [keys: Accessor<string[]>, other: { event: Accessor<KeyboardEvent | null> }]
16
+ * ```
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const [keys] = useKeyDownList();
21
+ * createEffect(() => {
22
+ * console.log(keys()) // => ["ALT", "CONTROL", "Q", "A"]
23
+ * })
24
+ * ```
25
+ */
26
+ declare const useKeyDownList: () => [keys: Accessor<string[]>, other: {
27
+ event: Accessor<KeyboardEvent | null>;
28
+ }];
29
+ /**
30
+ * Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`.
31
+ *
32
+ * This is a [shared root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSharedRoot). *(signals and event-listeners are reused across dependents)*
33
+ *
34
+ * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useCurrentlyHeldKey
35
+ *
36
+ * @returns
37
+ * ```ts
38
+ * Accessor<string | null>
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const key = useCurrentlyHeldKey();
44
+ * createEffect(() => {
45
+ * console.log(key()) // => "Q" | "ALT" | ... or null
46
+ * })
47
+ * ```
48
+ */
49
+ declare const useCurrentlyHeldKey: () => Accessor<string | null>;
3
50
  /**
4
- * Attaches keyboard event-listeners to `window`, and calls {@link onHoldChange} callback whenever user holds or stops holding specified {@link key}.
51
+ * Provides a signal with a sequence of currently held keys, as they were pressed down and up.
52
+ *
53
+ * This is a [shared root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSharedRoot). *(signals and event-listeners are reused across dependents)*
54
+ *
55
+ * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownSequence
56
+ *
57
+ * @returns
58
+ * ```ts
59
+ * Accessor<string[][]>
60
+ * // [["CONTROL"], ["CONTROL", "Q"], ["CONTROL", "Q", "A"]]
61
+ * ```
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const sequence = useKeyDownSequence();
66
+ * createEffect(() => {
67
+ * console.log(sequence()) // => string[][]
68
+ * })
69
+ * ```
70
+ */
71
+ declare const useKeyDownSequence: () => Accessor<string[][]>;
72
+ /**
73
+ * Provides a `boolean` signal indicating if provided {@link key} is currently being held down.
74
+ * Holding multiple keys at the same time will return `false` — holding only the specified one will return `true`.
75
+ *
76
+ * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createKeyHold
77
+ *
78
+ * @param key The key to check for.
79
+ * @options The options for the key hold.
80
+ * - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default.
81
+ * @returns
82
+ * ```ts
83
+ * Accessor<boolean>
84
+ * ```
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const isHeld = createKeyHold("ALT");
89
+ * createEffect(() => {
90
+ * console.log(isHeld()) // => boolean
91
+ * })
92
+ * ```
93
+ */
94
+ declare function createKeyHold(key: KbdKey, options?: {
95
+ preventDefault?: boolean;
96
+ }): Accessor<boolean>;
97
+ /**
98
+ * Creates a keyboard shotcut observer. The provided {@link callback} will be called when the specified {@link keys} are pressed.
99
+ *
100
+ * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createShortcut
5
101
  *
6
- * Event listeners are automatically cleaned on root dispose.
102
+ * @param keys The sequence of keys to watch for.
103
+ * @param callback The callback to call when the keys are pressed.
104
+ * @options The options for the shortcut.
105
+ * - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default.
106
+ * - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
7
107
  *
8
- * @param key keyboard key or modifier to listen for
9
- * @param onHoldChange callback fired when the hold state changes
10
- * @param options additional configuration
11
- * - `options.preventDefault` — call `e.preventDefault()` on the keyboard event, when the specified {@link key} is pressed. *(Defaults to `false`)*
12
- * - `options.allowOtherKeys` — Should the user be allowed to press other keys while holding the specified one *(Defaults to `false`)*
108
+ * @example
109
+ * ```ts
110
+ * createShortcut(["CONTROL", "SHIFT", "C"], () => {
111
+ * console.log("Ctrl+Shift+C was pressed");
112
+ * });
113
+ * ```
13
114
  */
14
- declare function makeKeyHoldListener(key: KeyToHold, onHoldChange: (isHolding: boolean) => void, options?: {
115
+ declare function createShortcut(keys: KbdKey[], callback: VoidFunction, options?: {
15
116
  preventDefault?: boolean;
16
- allowOtherKeys?: boolean;
117
+ requireReset?: boolean;
17
118
  }): void;
18
119
 
19
- export { KeyModifier, KeyToHold, makeKeyHoldListener };
120
+ export { KbdKey, ModifierKey, createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownList, useKeyDownSequence };
package/dist/index.js CHANGED
@@ -1,47 +1,127 @@
1
- // src/holdKeyListener.ts
1
+ "use strict";
2
+
3
+ // src/index.ts
2
4
  import { makeEventListener } from "@solid-primitives/event-listener";
3
- var keyModifiers = ["altKey", "ctrlKey", "metaKey", "shiftKey"];
4
- function makeKeyHoldListener(key, onHoldChange, options = {}) {
5
- const { preventDefault, allowOtherKeys } = options;
6
- const modifier = keyModifiers.includes(key) ? key : void 0;
7
- let state = false;
8
- let actualPressed = false;
9
- const updateState = (newState) => {
10
- newState !== state && onHoldChange(state = newState);
11
- };
12
- makeEventListener(window, "keydown", modifier ? (e) => {
5
+ import { createSharedRoot } from "@solid-primitives/rootless";
6
+ import { arrayEquals } from "@solid-primitives/utils";
7
+ import { batch, createEffect, createMemo, createSignal, on, untrack } from "solid-js";
8
+ function equalsKeyHoldSequence(sequence, model) {
9
+ for (let i = sequence.length - 1; i >= 0; i--) {
10
+ const _model = model.slice(0, i + 1);
11
+ if (!arrayEquals(sequence[i], _model))
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+ var useKeyDownList = /* @__PURE__ */ createSharedRoot(() => {
17
+ const [pressedKeys, setPressedKeys] = createSignal([]);
18
+ const [event, setEvent] = createSignal(null);
19
+ const reset = () => setPressedKeys([]);
20
+ makeEventListener(window, "keydown", (e) => {
13
21
  if (e.repeat)
14
22
  return;
15
- if (e[modifier]) {
16
- if (!state && !actualPressed) {
17
- preventDefault && e.preventDefault();
18
- onHoldChange(state = actualPressed = true);
19
- } else if (!allowOtherKeys)
20
- updateState(false);
21
- } else
22
- updateState(actualPressed = false);
23
- } : (e) => {
24
- if (e.key !== key) {
25
- allowOtherKeys || updateState(false);
26
- return;
27
- }
28
- if (e.repeat)
23
+ const key = e.key.toUpperCase();
24
+ if (pressedKeys().includes(key))
29
25
  return;
30
- updateState(true);
26
+ batch(() => {
27
+ setEvent(e);
28
+ setPressedKeys((prev) => [...prev, key]);
29
+ });
31
30
  });
32
- makeEventListener(window, "keyup", modifier ? (e) => {
33
- if (e[modifier])
34
- allowOtherKeys || updateState(false);
35
- else
36
- updateState(actualPressed = false);
37
- } : (e) => {
38
- if (e.key !== key)
39
- allowOtherKeys || updateState(false);
40
- else
41
- updateState(false);
31
+ makeEventListener(window, "keyup", (e) => {
32
+ const key = e.key.toUpperCase();
33
+ setPressedKeys((prev) => prev.filter((_key) => _key !== key));
34
+ });
35
+ makeEventListener(window, "blur", reset);
36
+ makeEventListener(window, "contextmenu", (e) => {
37
+ e.defaultPrevented || reset();
38
+ });
39
+ return [pressedKeys, { event }];
40
+ });
41
+ var useCurrentlyHeldKey = /* @__PURE__ */ createSharedRoot(() => {
42
+ const [keys] = useKeyDownList();
43
+ let prevKeys = untrack(keys);
44
+ return createMemo(() => {
45
+ const _keys = keys();
46
+ const prev = prevKeys;
47
+ prevKeys = _keys;
48
+ if (prev.length === 0 && _keys.length === 1)
49
+ return _keys[0];
50
+ return null;
51
+ });
52
+ });
53
+ var useKeyDownSequence = /* @__PURE__ */ createSharedRoot(() => {
54
+ const [keys] = useKeyDownList();
55
+ return createMemo((prev) => {
56
+ if (keys().length === 0)
57
+ return [];
58
+ return [...prev, keys()];
59
+ }, []);
60
+ });
61
+ function createKeyHold(key, options = {}) {
62
+ key = key.toUpperCase();
63
+ const { preventDefault = true } = options;
64
+ const [, { event }] = useKeyDownList();
65
+ const heldKey = useCurrentlyHeldKey();
66
+ return createMemo(() => {
67
+ if (heldKey() === key) {
68
+ preventDefault && event().preventDefault();
69
+ return true;
70
+ }
71
+ return false;
42
72
  });
43
- makeEventListener(document, "visibilitychange", () => document.visibilityState !== "visible" && updateState(false));
73
+ }
74
+ function createShortcut(keys, callback, options = {}) {
75
+ if (!keys.length)
76
+ return;
77
+ keys = keys.map((key) => key.toUpperCase());
78
+ const { preventDefault = true, requireReset = false } = options;
79
+ const [, { event }] = useKeyDownList();
80
+ const sequence = useKeyDownSequence();
81
+ let reset = false;
82
+ const handleSequenceWithReset = (sequence2) => {
83
+ if (!sequence2.length)
84
+ return reset = false;
85
+ if (reset)
86
+ return;
87
+ if (sequence2.length < keys.length) {
88
+ if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
89
+ preventDefault && event().preventDefault();
90
+ } else {
91
+ reset = true;
92
+ }
93
+ } else {
94
+ reset = true;
95
+ if (equalsKeyHoldSequence(sequence2, keys)) {
96
+ preventDefault && event().preventDefault();
97
+ callback();
98
+ }
99
+ }
100
+ };
101
+ const handleSequenceWithoutReset = (sequence2) => {
102
+ const last = sequence2.at(-1);
103
+ if (!last)
104
+ return;
105
+ if (preventDefault && last.length < keys.length) {
106
+ if (arrayEquals(last, keys.slice(0, keys.length - 1))) {
107
+ event().preventDefault();
108
+ }
109
+ return;
110
+ }
111
+ if (arrayEquals(last, keys)) {
112
+ const prev = sequence2.at(-2);
113
+ if (!prev || arrayEquals(prev, keys.slice(0, keys.length - 1))) {
114
+ preventDefault && event().preventDefault();
115
+ callback();
116
+ }
117
+ }
118
+ };
119
+ createEffect(on(sequence, requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
44
120
  }
45
121
  export {
46
- makeKeyHoldListener
122
+ createKeyHold,
123
+ createShortcut,
124
+ useCurrentlyHeldKey,
125
+ useKeyDownList,
126
+ useKeyDownSequence
47
127
  };
package/dist/server.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -19,12 +20,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
20
  // src/server.ts
20
21
  var server_exports = {};
21
22
  __export(server_exports, {
22
- makeKeyHoldListener: () => makeKeyHoldListener
23
+ createKeyHold: () => createKeyHold,
24
+ createShortcut: () => createShortcut,
25
+ useCurrentlyHeldKey: () => useCurrentlyHeldKey,
26
+ useKeyDownList: () => useKeyDownList,
27
+ useKeyDownSequence: () => useKeyDownSequence
23
28
  });
24
29
  module.exports = __toCommonJS(server_exports);
25
- var makeKeyHoldListener = () => {
26
- };
30
+ var import_utils = require("@solid-primitives/utils");
31
+ var useKeyDownList = () => [() => [], { event: () => null }];
32
+ var useCurrentlyHeldKey = () => () => null;
33
+ var useKeyDownSequence = () => () => [];
34
+ var createKeyHold = () => () => false;
35
+ var createShortcut = import_utils.noop;
27
36
  // Annotate the CommonJS export names for ESM import in node:
28
37
  0 && (module.exports = {
29
- makeKeyHoldListener
38
+ createKeyHold,
39
+ createShortcut,
40
+ useCurrentlyHeldKey,
41
+ useKeyDownList,
42
+ useKeyDownSequence
30
43
  });
package/dist/server.js CHANGED
@@ -1,6 +1,16 @@
1
+ "use strict";
2
+
1
3
  // src/server.ts
2
- var makeKeyHoldListener = () => {
3
- };
4
+ import { noop } from "@solid-primitives/utils";
5
+ var useKeyDownList = () => [() => [], { event: () => null }];
6
+ var useCurrentlyHeldKey = () => () => null;
7
+ var useKeyDownSequence = () => () => [];
8
+ var createKeyHold = () => () => false;
9
+ var createShortcut = noop;
4
10
  export {
5
- makeKeyHoldListener
11
+ createKeyHold,
12
+ createShortcut,
13
+ useCurrentlyHeldKey,
14
+ useKeyDownList,
15
+ useKeyDownSequence
6
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solid-primitives/keyboard",
3
- "version": "0.0.100",
3
+ "version": "1.0.0",
4
4
  "description": "A library of reactive promitives helping handling user's keyboard input.",
5
5
  "author": "Damian Tarnwski <gthetarnav@gmail.com>",
6
6
  "contributors": [],
@@ -17,7 +17,11 @@
17
17
  "name": "keyboard",
18
18
  "stage": 0,
19
19
  "list": [
20
- "makeKeyHoldListener"
20
+ "useKeyDownList",
21
+ "useCurrentlyHeldKey",
22
+ "useKeyDownSequence",
23
+ "createKeyHold",
24
+ "createShortcut"
21
25
  ],
22
26
  "category": "Inputs"
23
27
  },
@@ -42,8 +46,8 @@
42
46
  "start": "vite serve dev --host",
43
47
  "dev": "npm run start",
44
48
  "build": "tsup",
45
- "test": "uvu -r solid-register",
46
- "test:watch": "watchlist src test -- npm test"
49
+ "test": "vitest run test",
50
+ "test:watch": "vitest watch test"
47
51
  },
48
52
  "keywords": [
49
53
  "solid",
@@ -55,21 +59,21 @@
55
59
  "devDependencies": {
56
60
  "jsdom": "^19.0.0",
57
61
  "prettier": "^2.7.1",
58
- "solid-register": "^0.2.5",
62
+ "solid-js": "^1.4.4",
59
63
  "tslib": "^2.4.0",
60
64
  "tsup": "^6.1.2",
61
- "typescript": "^4.7.3",
65
+ "typescript": "^4.7.4",
62
66
  "unocss": "0.39.0",
63
- "uvu": "^0.5.3",
64
67
  "vite": "2.9.12",
65
68
  "vite-plugin-solid": "2.2.6",
66
- "watchlist": "^0.3.1",
67
- "solid-js": "^1.4.4"
69
+ "vitest": "^0.18.0"
68
70
  },
69
71
  "peerDependencies": {
70
72
  "solid-js": "^1.4.0"
71
73
  },
72
74
  "dependencies": {
73
- "@solid-primitives/event-listener": "^2.2.0"
75
+ "@solid-primitives/event-listener": "^2.2.0",
76
+ "@solid-primitives/rootless": "^1.1.0",
77
+ "@solid-primitives/utils": "^2.1.1"
74
78
  }
75
79
  }