@solid-primitives/keyboard 1.1.0 → 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,25 +110,15 @@ 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)
@@ -106,14 +128,14 @@ function createShortcut(keys, callback, options = {}) {
106
128
  const e = event();
107
129
  if (sequence2.length < keys.length) {
108
130
  if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
109
- preventDefault && e.preventDefault();
131
+ preventDefault && e && e.preventDefault();
110
132
  } else {
111
133
  reset = true;
112
134
  }
113
135
  } else {
114
136
  reset = true;
115
137
  if (equalsKeyHoldSequence(sequence2, keys)) {
116
- preventDefault && e.preventDefault();
138
+ preventDefault && e && e.preventDefault();
117
139
  callback(e);
118
140
  }
119
141
  }
@@ -125,19 +147,21 @@ function createShortcut(keys, callback, options = {}) {
125
147
  const e = event();
126
148
  if (preventDefault && last.length < keys.length) {
127
149
  if (utils.arrayEquals(last, keys.slice(0, keys.length - 1))) {
128
- e.preventDefault();
150
+ e && e.preventDefault();
129
151
  }
130
152
  return;
131
153
  }
132
154
  if (utils.arrayEquals(last, keys)) {
133
155
  const prev = sequence2.at(-2);
134
156
  if (!prev || utils.arrayEquals(prev, keys.slice(0, keys.length - 1))) {
135
- preventDefault && e.preventDefault();
157
+ preventDefault && e && e.preventDefault();
136
158
  callback(e);
137
159
  }
138
160
  }
139
161
  };
140
- solidJs.createEffect(solidJs.on(sequence, requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
162
+ solidJs.createEffect(
163
+ solidJs.on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset)
164
+ );
141
165
  }
142
166
 
143
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: (event: KeyboardEvent) => void, 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,25 +108,15 @@ 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)
@@ -104,14 +126,14 @@ function createShortcut(keys, callback, options = {}) {
104
126
  const e = event();
105
127
  if (sequence2.length < keys.length) {
106
128
  if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
107
- preventDefault && e.preventDefault();
129
+ preventDefault && e && e.preventDefault();
108
130
  } else {
109
131
  reset = true;
110
132
  }
111
133
  } else {
112
134
  reset = true;
113
135
  if (equalsKeyHoldSequence(sequence2, keys)) {
114
- preventDefault && e.preventDefault();
136
+ preventDefault && e && e.preventDefault();
115
137
  callback(e);
116
138
  }
117
139
  }
@@ -123,19 +145,21 @@ function createShortcut(keys, callback, options = {}) {
123
145
  const e = event();
124
146
  if (preventDefault && last.length < keys.length) {
125
147
  if (arrayEquals(last, keys.slice(0, keys.length - 1))) {
126
- e.preventDefault();
148
+ e && e.preventDefault();
127
149
  }
128
150
  return;
129
151
  }
130
152
  if (arrayEquals(last, keys)) {
131
153
  const prev = sequence2.at(-2);
132
154
  if (!prev || arrayEquals(prev, keys.slice(0, keys.length - 1))) {
133
- preventDefault && e.preventDefault();
155
+ preventDefault && e && e.preventDefault();
134
156
  callback(e);
135
157
  }
136
158
  }
137
159
  };
138
- createEffect(on(sequence, requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
160
+ createEffect(
161
+ on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset)
162
+ );
139
163
  }
140
164
 
141
- 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.1.0",
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"