@solid-primitives/keyboard 1.0.12 → 1.2.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,6 +11,7 @@
11
11
 
12
12
  A library of reactive promitives helping handling user's keyboard input.
13
13
 
14
+ - [`useKeyDownEvent`](#useKeyDownEvent) — Provides a signal with the last keydown event.
14
15
  - [`useKeyDownList`](#useKeyDownList) — Provides a signal with the list of currently held keys
15
16
  - [`useCurrentlyHeldKey`](#useCurrentlyHeldKey) — Provides a signal with the currently held single key.
16
17
  - [`useKeyDownSequence`](#useKeyDownSequence) — Provides a signal with a sequence of currently held keys, as they were pressed down and up.
@@ -22,9 +23,37 @@ A library of reactive promitives helping handling user's keyboard input.
22
23
  ```bash
23
24
  npm install @solid-primitives/keyboard
24
25
  # or
26
+ pnpm add @solid-primitives/keyboard
27
+ # or
25
28
  yarn add @solid-primitives/keyboard
26
29
  ```
27
30
 
31
+ ## `useKeyDownEvent`
32
+
33
+ Provides a signal with the last keydown event.
34
+
35
+ This is a [singleton root](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot) primitive that will reuse event listeners and signals across dependents.
36
+
37
+ ### How to use it
38
+
39
+ `useKeyDownEvent` takes no arguments, and returns a signal with the last keydown event.
40
+
41
+ ```tsx
42
+ import { useKeyDownEvent } from "@solid-primitives/keyboard";
43
+
44
+ const event = useKeyDownEvent();
45
+
46
+ createEffect(() => {
47
+ const e = event();
48
+ console.log(e); // => KeyboardEvent | null
49
+
50
+ if (e) {
51
+ console.log(e.key); // => "Q" | "ALT" | ... or null
52
+ e.preventDefault(); // prevent default behavior or last keydown event
53
+ }
54
+ });
55
+ ```
56
+
28
57
  ## `useKeyDownList`
29
58
 
30
59
  Provides a signal with the list of currently held keys, ordered from least recent to most recent.
@@ -33,16 +62,15 @@ This is a [singleton root](https://github.com/solidjs-community/solid-primitives
33
62
 
34
63
  ### How to use it
35
64
 
36
- `useKeyDownList` takes no arguments, and returns a signal with the list of currently held keys, and last keydown event.
65
+ `useKeyDownList` takes no arguments, and returns a signal with the list of currently held keys
37
66
 
38
67
  ```tsx
39
68
  import { useKeyDownList } from "@solid-primitives/keyboard";
40
69
 
41
- const [keys, { event }] = useKeyDownList();
70
+ const keys = useKeyDownList();
42
71
 
43
72
  createEffect(() => {
44
73
  console.log(keys()); // => string[] — list of currently held keys
45
- console.log(event()); // => KeyboardEvent | null — last keydown event
46
74
  });
47
75
 
48
76
  <For each={keys()}>
package/dist/index.cjs CHANGED
@@ -15,23 +15,49 @@ function equalsKeyHoldSequence(sequence, model) {
15
15
  }
16
16
  return true;
17
17
  }
18
+ exports.useKeyDownEvent = /* @__PURE__ */ rootless.createSingletonRoot(
19
+ () => {
20
+ if (web.isServer) {
21
+ return () => null;
22
+ }
23
+ const [event, setEvent] = solidJs.createSignal(null);
24
+ eventListener.makeEventListener(window, "keydown", (e) => {
25
+ setEvent(e);
26
+ setTimeout(() => setEvent(null));
27
+ });
28
+ return event;
29
+ }
30
+ );
18
31
  exports.useKeyDownList = /* @__PURE__ */ rootless.createSingletonRoot(() => {
19
32
  if (web.isServer) {
20
- return [() => [], { event: () => null }];
33
+ const keys = () => [];
34
+ keys[0] = keys;
35
+ keys[1] = { event: () => null };
36
+ keys[Symbol.iterator] = function* () {
37
+ yield keys[0];
38
+ yield keys[1];
39
+ };
40
+ return keys;
21
41
  }
22
- const [pressedKeys, setPressedKeys] = solidJs.createSignal([]);
23
- const [event, setEvent] = solidJs.createSignal(null);
24
- const reset = () => setPressedKeys([]);
42
+ const [pressedKeys, setPressedKeys] = solidJs.createSignal([]), reset = () => setPressedKeys([]), event = exports.useKeyDownEvent();
25
43
  eventListener.makeEventListener(window, "keydown", (e) => {
26
44
  if (e.repeat || typeof e.key !== "string")
27
45
  return;
28
- const key = e.key.toUpperCase();
29
- if (pressedKeys().includes(key))
46
+ const key = e.key.toUpperCase(), currentKeys = pressedKeys();
47
+ if (currentKeys.includes(key))
30
48
  return;
31
- solidJs.batch(() => {
32
- setEvent(e);
33
- setPressedKeys((prev) => [...prev, key]);
34
- });
49
+ const keys = [...currentKeys, key];
50
+ if (currentKeys.length === 0 && key !== "ALT" && key !== "CONTROL" && key !== "META" && key !== "SHIFT") {
51
+ if (e.shiftKey)
52
+ keys.unshift("SHIFT");
53
+ if (e.altKey)
54
+ keys.unshift("ALT");
55
+ if (e.ctrlKey)
56
+ keys.unshift("CONTROL");
57
+ if (e.metaKey)
58
+ keys.unshift("META");
59
+ }
60
+ setPressedKeys(keys);
35
61
  });
36
62
  eventListener.makeEventListener(window, "keyup", (e) => {
37
63
  if (typeof e.key !== "string")
@@ -43,14 +69,20 @@ exports.useKeyDownList = /* @__PURE__ */ rootless.createSingletonRoot(() => {
43
69
  eventListener.makeEventListener(window, "contextmenu", (e) => {
44
70
  e.defaultPrevented || reset();
45
71
  });
46
- return [pressedKeys, { event }];
72
+ pressedKeys[0] = pressedKeys;
73
+ pressedKeys[1] = { event };
74
+ pressedKeys[Symbol.iterator] = function* () {
75
+ yield pressedKeys[0];
76
+ yield pressedKeys[1];
77
+ };
78
+ return pressedKeys;
47
79
  });
48
80
  exports.useCurrentlyHeldKey = /* @__PURE__ */ rootless.createSingletonRoot(
49
81
  () => {
50
82
  if (web.isServer) {
51
83
  return () => null;
52
84
  }
53
- const [keys] = exports.useKeyDownList();
85
+ const keys = exports.useKeyDownList();
54
86
  let prevKeys = solidJs.untrack(keys);
55
87
  return solidJs.createMemo(() => {
56
88
  const _keys = keys();
@@ -66,7 +98,7 @@ exports.useKeyDownSequence = /* @__PURE__ */ rootless.createSingletonRoot(() =>
66
98
  if (web.isServer) {
67
99
  return () => [];
68
100
  }
69
- const [keys] = exports.useKeyDownList();
101
+ const keys = exports.useKeyDownList();
70
102
  return solidJs.createMemo((prev) => {
71
103
  if (keys().length === 0)
72
104
  return [];
@@ -78,42 +110,33 @@ function createKeyHold(key, options = {}) {
78
110
  return () => false;
79
111
  }
80
112
  key = key.toUpperCase();
81
- const { preventDefault = true } = options;
82
- const [, { event }] = exports.useKeyDownList();
83
- const heldKey = exports.useCurrentlyHeldKey();
84
- return solidJs.createMemo(() => {
85
- if (heldKey() === key) {
86
- preventDefault && event().preventDefault();
87
- return true;
88
- }
89
- return false;
90
- });
113
+ const { preventDefault = true } = options, event = exports.useKeyDownEvent(), heldKey = exports.useCurrentlyHeldKey();
114
+ return solidJs.createMemo(() => heldKey() === key && (preventDefault && event()?.preventDefault(), true));
91
115
  }
92
116
  function createShortcut(keys, callback, options = {}) {
93
117
  if (web.isServer || !keys.length) {
94
118
  return;
95
119
  }
96
120
  keys = keys.map((key) => key.toUpperCase());
97
- const { preventDefault = true, requireReset = false } = options;
98
- const [, { event }] = exports.useKeyDownList();
99
- const sequence = exports.useKeyDownSequence();
121
+ const { preventDefault = true } = options, event = exports.useKeyDownEvent(), sequence = exports.useKeyDownSequence();
100
122
  let reset = false;
101
123
  const handleSequenceWithReset = (sequence2) => {
102
124
  if (!sequence2.length)
103
125
  return reset = false;
104
126
  if (reset)
105
127
  return;
128
+ const e = event();
106
129
  if (sequence2.length < keys.length) {
107
130
  if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
108
- preventDefault && event().preventDefault();
131
+ preventDefault && e && e.preventDefault();
109
132
  } else {
110
133
  reset = true;
111
134
  }
112
135
  } else {
113
136
  reset = true;
114
137
  if (equalsKeyHoldSequence(sequence2, keys)) {
115
- preventDefault && event().preventDefault();
116
- callback();
138
+ preventDefault && e && e.preventDefault();
139
+ callback(e);
117
140
  }
118
141
  }
119
142
  };
@@ -121,21 +144,24 @@ function createShortcut(keys, callback, options = {}) {
121
144
  const last = sequence2.at(-1);
122
145
  if (!last)
123
146
  return;
147
+ const e = event();
124
148
  if (preventDefault && last.length < keys.length) {
125
149
  if (utils.arrayEquals(last, keys.slice(0, keys.length - 1))) {
126
- event().preventDefault();
150
+ e && e.preventDefault();
127
151
  }
128
152
  return;
129
153
  }
130
154
  if (utils.arrayEquals(last, keys)) {
131
155
  const prev = sequence2.at(-2);
132
156
  if (!prev || utils.arrayEquals(prev, keys.slice(0, keys.length - 1))) {
133
- preventDefault && event().preventDefault();
134
- callback();
157
+ preventDefault && e && e.preventDefault();
158
+ callback(e);
135
159
  }
136
160
  }
137
161
  };
138
- solidJs.createEffect(solidJs.on(sequence, requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
162
+ solidJs.createEffect(
163
+ solidJs.on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset)
164
+ );
139
165
  }
140
166
 
141
167
  exports.createKeyHold = createKeyHold;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,35 @@ import { Accessor } from 'solid-js';
2
2
 
3
3
  type ModifierKey = "Alt" | "Control" | "Meta" | "Shift";
4
4
  type KbdKey = ModifierKey | (string & {});
5
+ /**
6
+ * Provides a signal with the last keydown event.
7
+ *
8
+ * The signal is `null` initially, and is reset to that after a timeout.
9
+ *
10
+ * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownEvent
11
+ *
12
+ * @returns
13
+ * Returns a signal of the last keydown event
14
+ * ```ts
15
+ * Accessor<KeyboardEvent | null>
16
+ * ```
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const event = useKeyDownEvent();
21
+ *
22
+ * createEffect(() => {
23
+ * const e = event();
24
+ * console.log(e) // => KeyboardEvent | null
25
+ *
26
+ * if (e) {
27
+ * console.log(e.key) // => "Q" | "ALT" | ... or null
28
+ * e.preventDefault(); // prevent default behavior or last keydown event
29
+ * }
30
+ * })
31
+ * ```
32
+ */
33
+ declare const useKeyDownEvent: () => Accessor<KeyboardEvent | null>;
5
34
  /**
6
35
  * Provides a signal with the list of currently held keys, ordered from least recent to most recent.
7
36
  *
@@ -10,22 +39,20 @@ type KbdKey = ModifierKey | (string & {});
10
39
  * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownList
11
40
  *
12
41
  * @returns
13
- * Returns a signal of a list of keys, and a signal of last keydown event.
42
+ * Returns a signal of a list of keys
14
43
  * ```ts
15
- * [keys: Accessor<string[]>, other: { event: Accessor<KeyboardEvent | null> }]
44
+ * Accessor<string[]>
16
45
  * ```
17
46
  *
18
47
  * @example
19
48
  * ```ts
20
- * const [keys] = useKeyDownList();
49
+ * const keys = useKeyDownList();
21
50
  * createEffect(() => {
22
51
  * console.log(keys()) // => ["ALT", "CONTROL", "Q", "A"]
23
52
  * })
24
53
  * ```
25
54
  */
26
- declare const useKeyDownList: () => [keys: Accessor<string[]>, other: {
27
- event: Accessor<KeyboardEvent | null>;
28
- }];
55
+ declare const useKeyDownList: () => Accessor<string[]>;
29
56
  /**
30
57
  * Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`.
31
58
  *
@@ -112,9 +139,9 @@ declare function createKeyHold(key: KbdKey, options?: {
112
139
  * });
113
140
  * ```
114
141
  */
115
- declare function createShortcut(keys: KbdKey[], callback: VoidFunction, options?: {
142
+ declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: {
116
143
  preventDefault?: boolean;
117
144
  requireReset?: boolean;
118
145
  }): void;
119
146
 
120
- export { KbdKey, ModifierKey, createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownList, useKeyDownSequence };
147
+ export { KbdKey, ModifierKey, createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownEvent, useKeyDownList, useKeyDownSequence };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { makeEventListener } from '@solid-primitives/event-listener';
2
2
  import { createSingletonRoot } from '@solid-primitives/rootless';
3
3
  import { arrayEquals } from '@solid-primitives/utils';
4
- import { createSignal, batch, untrack, createMemo, createEffect, on } from 'solid-js';
4
+ import { createSignal, untrack, createMemo, createEffect, on } from 'solid-js';
5
5
  import { isServer } from 'solid-js/web';
6
6
 
7
7
  // src/index.ts
@@ -13,23 +13,49 @@ function equalsKeyHoldSequence(sequence, model) {
13
13
  }
14
14
  return true;
15
15
  }
16
+ var useKeyDownEvent = /* @__PURE__ */ createSingletonRoot(
17
+ () => {
18
+ if (isServer) {
19
+ return () => null;
20
+ }
21
+ const [event, setEvent] = createSignal(null);
22
+ makeEventListener(window, "keydown", (e) => {
23
+ setEvent(e);
24
+ setTimeout(() => setEvent(null));
25
+ });
26
+ return event;
27
+ }
28
+ );
16
29
  var useKeyDownList = /* @__PURE__ */ createSingletonRoot(() => {
17
30
  if (isServer) {
18
- return [() => [], { event: () => null }];
31
+ const keys = () => [];
32
+ keys[0] = keys;
33
+ keys[1] = { event: () => null };
34
+ keys[Symbol.iterator] = function* () {
35
+ yield keys[0];
36
+ yield keys[1];
37
+ };
38
+ return keys;
19
39
  }
20
- const [pressedKeys, setPressedKeys] = createSignal([]);
21
- const [event, setEvent] = createSignal(null);
22
- const reset = () => setPressedKeys([]);
40
+ const [pressedKeys, setPressedKeys] = createSignal([]), reset = () => setPressedKeys([]), event = useKeyDownEvent();
23
41
  makeEventListener(window, "keydown", (e) => {
24
42
  if (e.repeat || typeof e.key !== "string")
25
43
  return;
26
- const key = e.key.toUpperCase();
27
- if (pressedKeys().includes(key))
44
+ const key = e.key.toUpperCase(), currentKeys = pressedKeys();
45
+ if (currentKeys.includes(key))
28
46
  return;
29
- batch(() => {
30
- setEvent(e);
31
- setPressedKeys((prev) => [...prev, key]);
32
- });
47
+ const keys = [...currentKeys, key];
48
+ if (currentKeys.length === 0 && key !== "ALT" && key !== "CONTROL" && key !== "META" && key !== "SHIFT") {
49
+ if (e.shiftKey)
50
+ keys.unshift("SHIFT");
51
+ if (e.altKey)
52
+ keys.unshift("ALT");
53
+ if (e.ctrlKey)
54
+ keys.unshift("CONTROL");
55
+ if (e.metaKey)
56
+ keys.unshift("META");
57
+ }
58
+ setPressedKeys(keys);
33
59
  });
34
60
  makeEventListener(window, "keyup", (e) => {
35
61
  if (typeof e.key !== "string")
@@ -41,14 +67,20 @@ var useKeyDownList = /* @__PURE__ */ createSingletonRoot(() => {
41
67
  makeEventListener(window, "contextmenu", (e) => {
42
68
  e.defaultPrevented || reset();
43
69
  });
44
- return [pressedKeys, { event }];
70
+ pressedKeys[0] = pressedKeys;
71
+ pressedKeys[1] = { event };
72
+ pressedKeys[Symbol.iterator] = function* () {
73
+ yield pressedKeys[0];
74
+ yield pressedKeys[1];
75
+ };
76
+ return pressedKeys;
45
77
  });
46
78
  var useCurrentlyHeldKey = /* @__PURE__ */ createSingletonRoot(
47
79
  () => {
48
80
  if (isServer) {
49
81
  return () => null;
50
82
  }
51
- const [keys] = useKeyDownList();
83
+ const keys = useKeyDownList();
52
84
  let prevKeys = untrack(keys);
53
85
  return createMemo(() => {
54
86
  const _keys = keys();
@@ -64,7 +96,7 @@ var useKeyDownSequence = /* @__PURE__ */ createSingletonRoot(() => {
64
96
  if (isServer) {
65
97
  return () => [];
66
98
  }
67
- const [keys] = useKeyDownList();
99
+ const keys = useKeyDownList();
68
100
  return createMemo((prev) => {
69
101
  if (keys().length === 0)
70
102
  return [];
@@ -76,42 +108,33 @@ function createKeyHold(key, options = {}) {
76
108
  return () => false;
77
109
  }
78
110
  key = key.toUpperCase();
79
- const { preventDefault = true } = options;
80
- const [, { event }] = useKeyDownList();
81
- const heldKey = useCurrentlyHeldKey();
82
- return createMemo(() => {
83
- if (heldKey() === key) {
84
- preventDefault && event().preventDefault();
85
- return true;
86
- }
87
- return false;
88
- });
111
+ const { preventDefault = true } = options, event = useKeyDownEvent(), heldKey = useCurrentlyHeldKey();
112
+ return createMemo(() => heldKey() === key && (preventDefault && event()?.preventDefault(), true));
89
113
  }
90
114
  function createShortcut(keys, callback, options = {}) {
91
115
  if (isServer || !keys.length) {
92
116
  return;
93
117
  }
94
118
  keys = keys.map((key) => key.toUpperCase());
95
- const { preventDefault = true, requireReset = false } = options;
96
- const [, { event }] = useKeyDownList();
97
- const sequence = useKeyDownSequence();
119
+ const { preventDefault = true } = options, event = useKeyDownEvent(), sequence = useKeyDownSequence();
98
120
  let reset = false;
99
121
  const handleSequenceWithReset = (sequence2) => {
100
122
  if (!sequence2.length)
101
123
  return reset = false;
102
124
  if (reset)
103
125
  return;
126
+ const e = event();
104
127
  if (sequence2.length < keys.length) {
105
128
  if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
106
- preventDefault && event().preventDefault();
129
+ preventDefault && e && e.preventDefault();
107
130
  } else {
108
131
  reset = true;
109
132
  }
110
133
  } else {
111
134
  reset = true;
112
135
  if (equalsKeyHoldSequence(sequence2, keys)) {
113
- preventDefault && event().preventDefault();
114
- callback();
136
+ preventDefault && e && e.preventDefault();
137
+ callback(e);
115
138
  }
116
139
  }
117
140
  };
@@ -119,21 +142,24 @@ function createShortcut(keys, callback, options = {}) {
119
142
  const last = sequence2.at(-1);
120
143
  if (!last)
121
144
  return;
145
+ const e = event();
122
146
  if (preventDefault && last.length < keys.length) {
123
147
  if (arrayEquals(last, keys.slice(0, keys.length - 1))) {
124
- event().preventDefault();
148
+ e && e.preventDefault();
125
149
  }
126
150
  return;
127
151
  }
128
152
  if (arrayEquals(last, keys)) {
129
153
  const prev = sequence2.at(-2);
130
154
  if (!prev || arrayEquals(prev, keys.slice(0, keys.length - 1))) {
131
- preventDefault && event().preventDefault();
132
- callback();
155
+ preventDefault && e && e.preventDefault();
156
+ callback(e);
133
157
  }
134
158
  }
135
159
  };
136
- createEffect(on(sequence, requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
160
+ createEffect(
161
+ on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset)
162
+ );
137
163
  }
138
164
 
139
- export { createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownList, useKeyDownSequence };
165
+ export { createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownEvent, useKeyDownList, useKeyDownSequence };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solid-primitives/keyboard",
3
- "version": "1.0.12",
3
+ "version": "1.2.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": [],
@@ -52,7 +52,7 @@
52
52
  "dependencies": {
53
53
  "@solid-primitives/event-listener": "^2.2.10",
54
54
  "@solid-primitives/rootless": "^1.3.2",
55
- "@solid-primitives/utils": "^6.0.0"
55
+ "@solid-primitives/utils": "^6.1.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "solid-js": "^1.6.12"