@solid-primitives/keyboard 1.2.8 → 1.3.1
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 +7 -9
- package/dist/index.d.ts +9 -12
- package/dist/index.js +283 -145
- package/package.json +5 -9
- package/dist/index.cjs +0 -168
- package/dist/index.d.cts +0 -147
package/README.md
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
A library of reactive promitives helping handling user's keyboard input.
|
|
13
13
|
|
|
14
|
-
- [`useKeyDownEvent`](#
|
|
15
|
-
- [`useKeyDownList`](#
|
|
16
|
-
- [`useCurrentlyHeldKey`](#
|
|
17
|
-
- [`useKeyDownSequence`](#
|
|
18
|
-
- [`createKeyHold`](#
|
|
19
|
-
- [`createShortcut`](#
|
|
14
|
+
- [`useKeyDownEvent`](#usekeydownevent) — Provides a signal with the last keydown event.
|
|
15
|
+
- [`useKeyDownList`](#usekeydownlist) — Provides a signal with the list of currently held keys
|
|
16
|
+
- [`useCurrentlyHeldKey`](#usecurrentlyheldkey) — Provides a signal with the currently held single key.
|
|
17
|
+
- [`useKeyDownSequence`](#usekeydownsequence) — Provides a signal with a sequence of currently held keys, as they were pressed down and up.
|
|
18
|
+
- [`createKeyHold`](#createkeyhold) — Provides a signal indicating if provided key is currently being held down.
|
|
19
|
+
- [`createShortcut`](#createshortcut) — Creates a keyboard shotcut observer.
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -73,9 +73,7 @@ createEffect(() => {
|
|
|
73
73
|
console.log(keys()); // => string[] — list of currently held keys
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
<For each={keys()}>
|
|
77
|
-
{key => <kbd>{key}</kdb>}
|
|
78
|
-
</For>
|
|
76
|
+
<For each={keys()}>{key => <kbd>{key}</kbd>}</For>;
|
|
79
77
|
```
|
|
80
78
|
|
|
81
79
|
## `useCurrentlyHeldKey`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Accessor } from
|
|
2
|
-
|
|
3
|
-
type
|
|
4
|
-
type KbdKey = ModifierKey | (string & {});
|
|
1
|
+
import { Accessor } from "solid-js";
|
|
2
|
+
export type ModifierKey = "Alt" | "Control" | "Meta" | "Shift";
|
|
3
|
+
export type KbdKey = ModifierKey | (string & {});
|
|
5
4
|
/**
|
|
6
5
|
* Provides a signal with the last keydown event.
|
|
7
6
|
*
|
|
@@ -30,7 +29,7 @@ type KbdKey = ModifierKey | (string & {});
|
|
|
30
29
|
* })
|
|
31
30
|
* ```
|
|
32
31
|
*/
|
|
33
|
-
declare const useKeyDownEvent: () => Accessor<KeyboardEvent | null>;
|
|
32
|
+
export declare const useKeyDownEvent: () => Accessor<KeyboardEvent | null>;
|
|
34
33
|
/**
|
|
35
34
|
* Provides a signal with the list of currently held keys, ordered from least recent to most recent.
|
|
36
35
|
*
|
|
@@ -52,7 +51,7 @@ declare const useKeyDownEvent: () => Accessor<KeyboardEvent | null>;
|
|
|
52
51
|
* })
|
|
53
52
|
* ```
|
|
54
53
|
*/
|
|
55
|
-
declare const useKeyDownList: () => Accessor<string[]>;
|
|
54
|
+
export declare const useKeyDownList: () => Accessor<string[]>;
|
|
56
55
|
/**
|
|
57
56
|
* Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`.
|
|
58
57
|
*
|
|
@@ -73,7 +72,7 @@ declare const useKeyDownList: () => Accessor<string[]>;
|
|
|
73
72
|
* })
|
|
74
73
|
* ```
|
|
75
74
|
*/
|
|
76
|
-
declare const useCurrentlyHeldKey: () => Accessor<string | null>;
|
|
75
|
+
export declare const useCurrentlyHeldKey: () => Accessor<string | null>;
|
|
77
76
|
/**
|
|
78
77
|
* Provides a signal with a sequence of currently held keys, as they were pressed down and up.
|
|
79
78
|
*
|
|
@@ -95,7 +94,7 @@ declare const useCurrentlyHeldKey: () => Accessor<string | null>;
|
|
|
95
94
|
* })
|
|
96
95
|
* ```
|
|
97
96
|
*/
|
|
98
|
-
declare const useKeyDownSequence: () => Accessor<string[][]>;
|
|
97
|
+
export declare const useKeyDownSequence: () => Accessor<string[][]>;
|
|
99
98
|
/**
|
|
100
99
|
* Provides a `boolean` signal indicating if provided {@link key} is currently being held down.
|
|
101
100
|
* Holding multiple keys at the same time will return `false` — holding only the specified one will return `true`.
|
|
@@ -118,7 +117,7 @@ declare const useKeyDownSequence: () => Accessor<string[][]>;
|
|
|
118
117
|
* })
|
|
119
118
|
* ```
|
|
120
119
|
*/
|
|
121
|
-
declare function createKeyHold(key: KbdKey, options?: {
|
|
120
|
+
export declare function createKeyHold(key: KbdKey, options?: {
|
|
122
121
|
preventDefault?: boolean;
|
|
123
122
|
}): Accessor<boolean>;
|
|
124
123
|
/**
|
|
@@ -139,9 +138,7 @@ declare function createKeyHold(key: KbdKey, options?: {
|
|
|
139
138
|
* });
|
|
140
139
|
* ```
|
|
141
140
|
*/
|
|
142
|
-
declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: {
|
|
141
|
+
export declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: {
|
|
143
142
|
preventDefault?: boolean;
|
|
144
143
|
requireReset?: boolean;
|
|
145
144
|
}): void;
|
|
146
|
-
|
|
147
|
-
export { KbdKey, ModifierKey, createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownEvent, useKeyDownList, useKeyDownSequence };
|
package/dist/index.js
CHANGED
|
@@ -1,165 +1,303 @@
|
|
|
1
|
-
import { makeEventListener } from
|
|
2
|
-
import { createSingletonRoot } from
|
|
3
|
-
import { arrayEquals } from
|
|
4
|
-
import {
|
|
5
|
-
import { isServer } from
|
|
6
|
-
|
|
7
|
-
// src/index.ts
|
|
1
|
+
import { makeEventListener } from "@solid-primitives/event-listener";
|
|
2
|
+
import { createSingletonRoot } from "@solid-primitives/rootless";
|
|
3
|
+
import { arrayEquals } from "@solid-primitives/utils";
|
|
4
|
+
import { createEffect, createMemo, createSignal, on, untrack } from "solid-js";
|
|
5
|
+
import { isServer } from "solid-js/web";
|
|
8
6
|
function equalsKeyHoldSequence(sequence, model) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
for (let i = sequence.length - 1; i >= 0; i--) {
|
|
8
|
+
const _model = model.slice(0, i + 1);
|
|
9
|
+
if (!arrayEquals(sequence[i], _model))
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
15
13
|
}
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Provides a signal with the last keydown event.
|
|
16
|
+
*
|
|
17
|
+
* The signal is `null` initially, and is reset to that after a timeout.
|
|
18
|
+
*
|
|
19
|
+
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownEvent
|
|
20
|
+
*
|
|
21
|
+
* @returns
|
|
22
|
+
* Returns a signal of the last keydown event
|
|
23
|
+
* ```ts
|
|
24
|
+
* Accessor<KeyboardEvent | null>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const event = useKeyDownEvent();
|
|
30
|
+
*
|
|
31
|
+
* createEffect(() => {
|
|
32
|
+
* const e = event();
|
|
33
|
+
* console.log(e) // => KeyboardEvent | null
|
|
34
|
+
*
|
|
35
|
+
* if (e) {
|
|
36
|
+
* console.log(e.key) // => "Q" | "ALT" | ... or null
|
|
37
|
+
* e.preventDefault(); // prevent default behavior or last keydown event
|
|
38
|
+
* }
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const useKeyDownEvent = /*#__PURE__*/ createSingletonRoot(() => {
|
|
18
43
|
if (isServer) {
|
|
19
|
-
|
|
44
|
+
return () => null;
|
|
20
45
|
}
|
|
21
46
|
const [event, setEvent] = createSignal(null);
|
|
22
|
-
makeEventListener(window, "keydown",
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
makeEventListener(window, "keydown", e => {
|
|
48
|
+
setEvent(e);
|
|
49
|
+
setTimeout(() => setEvent(null));
|
|
25
50
|
});
|
|
26
51
|
return event;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
keys
|
|
55
|
-
|
|
56
|
-
keys.
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Provides a signal with the list of currently held keys, ordered from least recent to most recent.
|
|
55
|
+
*
|
|
56
|
+
* This is a [singleton root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot). *(signals and event-listeners are reused across dependents)*
|
|
57
|
+
*
|
|
58
|
+
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownList
|
|
59
|
+
*
|
|
60
|
+
* @returns
|
|
61
|
+
* Returns a signal of a list of keys
|
|
62
|
+
* ```ts
|
|
63
|
+
* Accessor<string[]>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const keys = useKeyDownList();
|
|
69
|
+
* createEffect(() => {
|
|
70
|
+
* console.log(keys()) // => ["ALT", "CONTROL", "Q", "A"]
|
|
71
|
+
* })
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export const useKeyDownList = /*#__PURE__*/ createSingletonRoot(() => {
|
|
75
|
+
if (isServer) {
|
|
76
|
+
const keys = () => [];
|
|
77
|
+
// this is for backwards compatibility
|
|
78
|
+
// TODO remove in the next major version
|
|
79
|
+
keys[0] = keys;
|
|
80
|
+
keys[1] = { event: () => null };
|
|
81
|
+
keys[Symbol.iterator] = function* () {
|
|
82
|
+
yield keys[0];
|
|
83
|
+
yield keys[1];
|
|
84
|
+
};
|
|
85
|
+
return keys;
|
|
57
86
|
}
|
|
58
|
-
setPressedKeys(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
const [pressedKeys, setPressedKeys] = createSignal([]), reset = () => setPressedKeys([]), event = useKeyDownEvent();
|
|
88
|
+
makeEventListener(window, "keydown", e => {
|
|
89
|
+
// e.key may be undefined when used with <datalist> el
|
|
90
|
+
// gh issue: https://github.com/solidjs-community/solid-primitives/issues/246
|
|
91
|
+
if (e.repeat || typeof e.key !== "string")
|
|
92
|
+
return;
|
|
93
|
+
const key = e.key.toUpperCase(), currentKeys = pressedKeys();
|
|
94
|
+
if (currentKeys.includes(key))
|
|
95
|
+
return;
|
|
96
|
+
const keys = [...currentKeys, key];
|
|
97
|
+
// if the modifier is pressed before we start listening
|
|
98
|
+
// we should add it to the list
|
|
99
|
+
if (currentKeys.length === 0 &&
|
|
100
|
+
key !== "ALT" &&
|
|
101
|
+
key !== "CONTROL" &&
|
|
102
|
+
key !== "META" &&
|
|
103
|
+
key !== "SHIFT") {
|
|
104
|
+
if (e.shiftKey)
|
|
105
|
+
keys.unshift("SHIFT");
|
|
106
|
+
if (e.altKey)
|
|
107
|
+
keys.unshift("ALT");
|
|
108
|
+
if (e.ctrlKey)
|
|
109
|
+
keys.unshift("CONTROL");
|
|
110
|
+
if (e.metaKey)
|
|
111
|
+
keys.unshift("META");
|
|
112
|
+
}
|
|
113
|
+
setPressedKeys(keys);
|
|
114
|
+
});
|
|
115
|
+
makeEventListener(window, "keyup", e => {
|
|
116
|
+
if (typeof e.key !== "string")
|
|
117
|
+
return;
|
|
118
|
+
const key = e.key.toUpperCase();
|
|
119
|
+
setPressedKeys(prev => prev.filter(_key => _key !== key));
|
|
120
|
+
});
|
|
121
|
+
makeEventListener(window, "blur", reset);
|
|
122
|
+
makeEventListener(window, "contextmenu", e => {
|
|
123
|
+
e.defaultPrevented || reset();
|
|
124
|
+
});
|
|
125
|
+
// this is for backwards compatibility
|
|
126
|
+
// TODO remove in the next major version
|
|
127
|
+
pressedKeys[0] = pressedKeys;
|
|
128
|
+
pressedKeys[1] = { event };
|
|
129
|
+
pressedKeys[Symbol.iterator] = function* () {
|
|
130
|
+
yield pressedKeys[0];
|
|
131
|
+
yield pressedKeys[1];
|
|
132
|
+
};
|
|
133
|
+
return pressedKeys;
|
|
77
134
|
});
|
|
78
|
-
|
|
79
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`.
|
|
137
|
+
*
|
|
138
|
+
* This is a [singleton root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot). *(signals and event-listeners are reused across dependents)*
|
|
139
|
+
*
|
|
140
|
+
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useCurrentlyHeldKey
|
|
141
|
+
*
|
|
142
|
+
* @returns
|
|
143
|
+
* ```ts
|
|
144
|
+
* Accessor<string | null>
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* const key = useCurrentlyHeldKey();
|
|
150
|
+
* createEffect(() => {
|
|
151
|
+
* console.log(key()) // => "Q" | "ALT" | ... or null
|
|
152
|
+
* })
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export const useCurrentlyHeldKey = /*#__PURE__*/ createSingletonRoot(() => {
|
|
80
156
|
if (isServer) {
|
|
81
|
-
|
|
157
|
+
return () => null;
|
|
82
158
|
}
|
|
83
159
|
const keys = useKeyDownList();
|
|
84
160
|
let prevKeys = untrack(keys);
|
|
85
161
|
return createMemo(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
162
|
+
const _keys = keys();
|
|
163
|
+
const prev = prevKeys;
|
|
164
|
+
prevKeys = _keys;
|
|
165
|
+
if (prev.length === 0 && _keys.length === 1)
|
|
166
|
+
return _keys[0];
|
|
167
|
+
return null;
|
|
92
168
|
});
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
var useKeyDownSequence = /* @__PURE__ */ createSingletonRoot(() => {
|
|
96
|
-
if (isServer) {
|
|
97
|
-
return () => [];
|
|
98
|
-
}
|
|
99
|
-
const keys = useKeyDownList();
|
|
100
|
-
return createMemo((prev) => {
|
|
101
|
-
if (keys().length === 0)
|
|
102
|
-
return [];
|
|
103
|
-
return [...prev, keys()];
|
|
104
|
-
}, []);
|
|
105
169
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
} else {
|
|
131
|
-
reset = true;
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
reset = true;
|
|
135
|
-
if (equalsKeyHoldSequence(sequence2, keys)) {
|
|
136
|
-
preventDefault && e && e.preventDefault();
|
|
137
|
-
callback(e);
|
|
138
|
-
}
|
|
170
|
+
/**
|
|
171
|
+
* Provides a signal with a sequence of currently held keys, as they were pressed down and up.
|
|
172
|
+
*
|
|
173
|
+
* This is a [singleton root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot). *(signals and event-listeners are reused across dependents)*
|
|
174
|
+
*
|
|
175
|
+
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownSequence
|
|
176
|
+
*
|
|
177
|
+
* @returns
|
|
178
|
+
* ```ts
|
|
179
|
+
* Accessor<string[][]>
|
|
180
|
+
* // [["CONTROL"], ["CONTROL", "Q"], ["CONTROL", "Q", "A"]]
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const sequence = useKeyDownSequence();
|
|
186
|
+
* createEffect(() => {
|
|
187
|
+
* console.log(sequence()) // => string[][]
|
|
188
|
+
* })
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export const useKeyDownSequence = /*#__PURE__*/ createSingletonRoot(() => {
|
|
192
|
+
if (isServer) {
|
|
193
|
+
return () => [];
|
|
139
194
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
195
|
+
const keys = useKeyDownList();
|
|
196
|
+
return createMemo(prev => {
|
|
197
|
+
if (keys().length === 0)
|
|
198
|
+
return [];
|
|
199
|
+
return [...prev, keys()];
|
|
200
|
+
}, []);
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* Provides a `boolean` signal indicating if provided {@link key} is currently being held down.
|
|
204
|
+
* Holding multiple keys at the same time will return `false` — holding only the specified one will return `true`.
|
|
205
|
+
*
|
|
206
|
+
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createKeyHold
|
|
207
|
+
*
|
|
208
|
+
* @param key The key to check for.
|
|
209
|
+
* @options The options for the key hold.
|
|
210
|
+
* - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default.
|
|
211
|
+
* @returns
|
|
212
|
+
* ```ts
|
|
213
|
+
* Accessor<boolean>
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const isHeld = createKeyHold("ALT");
|
|
219
|
+
* createEffect(() => {
|
|
220
|
+
* console.log(isHeld()) // => boolean
|
|
221
|
+
* })
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export function createKeyHold(key, options = {}) {
|
|
225
|
+
if (isServer) {
|
|
226
|
+
return () => false;
|
|
151
227
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
228
|
+
key = key.toUpperCase();
|
|
229
|
+
const { preventDefault = true } = options, event = useKeyDownEvent(), heldKey = useCurrentlyHeldKey();
|
|
230
|
+
return createMemo(() => heldKey() === key && (preventDefault && event()?.preventDefault(), true));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Creates a keyboard shotcut observer. The provided {@link callback} will be called when the specified {@link keys} are pressed.
|
|
234
|
+
*
|
|
235
|
+
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createShortcut
|
|
236
|
+
*
|
|
237
|
+
* @param keys The sequence of keys to watch for.
|
|
238
|
+
* @param callback The callback to call when the keys are pressed.
|
|
239
|
+
* @options The options for the shortcut.
|
|
240
|
+
* - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default.
|
|
241
|
+
* - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* createShortcut(["CONTROL", "SHIFT", "C"], () => {
|
|
246
|
+
* console.log("Ctrl+Shift+C was pressed");
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export function createShortcut(keys, callback, options = {}) {
|
|
251
|
+
if (isServer || !keys.length) {
|
|
252
|
+
return;
|
|
158
253
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
254
|
+
keys = keys.map(key => key.toUpperCase());
|
|
255
|
+
const { preventDefault = true } = options, event = useKeyDownEvent(), sequence = useKeyDownSequence();
|
|
256
|
+
let reset = false;
|
|
257
|
+
// allow to check the sequence only once the user has released all keys
|
|
258
|
+
const handleSequenceWithReset = (sequence) => {
|
|
259
|
+
if (!sequence.length)
|
|
260
|
+
return (reset = false);
|
|
261
|
+
if (reset)
|
|
262
|
+
return;
|
|
263
|
+
const e = event();
|
|
264
|
+
if (sequence.length < keys.length) {
|
|
265
|
+
// optimistically preventDefault behavior if we yet don't have enough keys
|
|
266
|
+
if (equalsKeyHoldSequence(sequence, keys.slice(0, sequence.length))) {
|
|
267
|
+
preventDefault && e && e.preventDefault();
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
reset = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
reset = true;
|
|
275
|
+
if (equalsKeyHoldSequence(sequence, keys)) {
|
|
276
|
+
preventDefault && e && e.preventDefault();
|
|
277
|
+
callback(e);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
// allow checking the sequence even if the user is still holding down keys
|
|
282
|
+
const handleSequenceWithoutReset = (sequence) => {
|
|
283
|
+
const last = sequence.at(-1);
|
|
284
|
+
if (!last)
|
|
285
|
+
return;
|
|
286
|
+
const e = event();
|
|
287
|
+
// optimistically preventDefault behavior if we yet don't have enough keys
|
|
288
|
+
if (preventDefault && last.length < keys.length) {
|
|
289
|
+
if (arrayEquals(last, keys.slice(0, keys.length - 1))) {
|
|
290
|
+
e && e.preventDefault();
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (arrayEquals(last, keys)) {
|
|
295
|
+
const prev = sequence.at(-2);
|
|
296
|
+
if (!prev || arrayEquals(prev, keys.slice(0, keys.length - 1))) {
|
|
297
|
+
preventDefault && e && e.preventDefault();
|
|
298
|
+
callback(e);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
createEffect(on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset));
|
|
163
303
|
}
|
|
164
|
-
|
|
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.
|
|
3
|
+
"version": "1.3.1",
|
|
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": [],
|
|
@@ -38,24 +38,20 @@
|
|
|
38
38
|
"dist"
|
|
39
39
|
],
|
|
40
40
|
"type": "module",
|
|
41
|
-
"main": "./dist/index.cjs",
|
|
42
41
|
"module": "./dist/index.js",
|
|
43
42
|
"types": "./dist/index.d.ts",
|
|
44
43
|
"browser": {},
|
|
45
44
|
"exports": {
|
|
46
45
|
"import": {
|
|
46
|
+
"@solid-primitives/source": "./src/index.ts",
|
|
47
47
|
"types": "./dist/index.d.ts",
|
|
48
48
|
"default": "./dist/index.js"
|
|
49
|
-
},
|
|
50
|
-
"require": {
|
|
51
|
-
"types": "./dist/index.d.cts",
|
|
52
|
-
"default": "./dist/index.cjs"
|
|
53
49
|
}
|
|
54
50
|
},
|
|
55
51
|
"dependencies": {
|
|
56
|
-
"@solid-primitives/event-listener": "^2.
|
|
57
|
-
"@solid-primitives/
|
|
58
|
-
"@solid-primitives/
|
|
52
|
+
"@solid-primitives/event-listener": "^2.4.1",
|
|
53
|
+
"@solid-primitives/rootless": "^1.5.1",
|
|
54
|
+
"@solid-primitives/utils": "^6.3.1"
|
|
59
55
|
},
|
|
60
56
|
"peerDependencies": {
|
|
61
57
|
"solid-js": "^1.6.12"
|
package/dist/index.cjs
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var eventListener = require('@solid-primitives/event-listener');
|
|
4
|
-
var rootless = require('@solid-primitives/rootless');
|
|
5
|
-
var utils = require('@solid-primitives/utils');
|
|
6
|
-
var solidJs = require('solid-js');
|
|
7
|
-
var web = require('solid-js/web');
|
|
8
|
-
|
|
9
|
-
// src/index.ts
|
|
10
|
-
function equalsKeyHoldSequence(sequence, model) {
|
|
11
|
-
for (let i = sequence.length - 1; i >= 0; i--) {
|
|
12
|
-
const _model = model.slice(0, i + 1);
|
|
13
|
-
if (!utils.arrayEquals(sequence[i], _model))
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
return true;
|
|
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
|
-
);
|
|
31
|
-
exports.useKeyDownList = /* @__PURE__ */ rootless.createSingletonRoot(() => {
|
|
32
|
-
if (web.isServer) {
|
|
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;
|
|
41
|
-
}
|
|
42
|
-
const [pressedKeys, setPressedKeys] = solidJs.createSignal([]), reset = () => setPressedKeys([]), event = exports.useKeyDownEvent();
|
|
43
|
-
eventListener.makeEventListener(window, "keydown", (e) => {
|
|
44
|
-
if (e.repeat || typeof e.key !== "string")
|
|
45
|
-
return;
|
|
46
|
-
const key = e.key.toUpperCase(), currentKeys = pressedKeys();
|
|
47
|
-
if (currentKeys.includes(key))
|
|
48
|
-
return;
|
|
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);
|
|
61
|
-
});
|
|
62
|
-
eventListener.makeEventListener(window, "keyup", (e) => {
|
|
63
|
-
if (typeof e.key !== "string")
|
|
64
|
-
return;
|
|
65
|
-
const key = e.key.toUpperCase();
|
|
66
|
-
setPressedKeys((prev) => prev.filter((_key) => _key !== key));
|
|
67
|
-
});
|
|
68
|
-
eventListener.makeEventListener(window, "blur", reset);
|
|
69
|
-
eventListener.makeEventListener(window, "contextmenu", (e) => {
|
|
70
|
-
e.defaultPrevented || reset();
|
|
71
|
-
});
|
|
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;
|
|
79
|
-
});
|
|
80
|
-
exports.useCurrentlyHeldKey = /* @__PURE__ */ rootless.createSingletonRoot(
|
|
81
|
-
() => {
|
|
82
|
-
if (web.isServer) {
|
|
83
|
-
return () => null;
|
|
84
|
-
}
|
|
85
|
-
const keys = exports.useKeyDownList();
|
|
86
|
-
let prevKeys = solidJs.untrack(keys);
|
|
87
|
-
return solidJs.createMemo(() => {
|
|
88
|
-
const _keys = keys();
|
|
89
|
-
const prev = prevKeys;
|
|
90
|
-
prevKeys = _keys;
|
|
91
|
-
if (prev.length === 0 && _keys.length === 1)
|
|
92
|
-
return _keys[0];
|
|
93
|
-
return null;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
);
|
|
97
|
-
exports.useKeyDownSequence = /* @__PURE__ */ rootless.createSingletonRoot(() => {
|
|
98
|
-
if (web.isServer) {
|
|
99
|
-
return () => [];
|
|
100
|
-
}
|
|
101
|
-
const keys = exports.useKeyDownList();
|
|
102
|
-
return solidJs.createMemo((prev) => {
|
|
103
|
-
if (keys().length === 0)
|
|
104
|
-
return [];
|
|
105
|
-
return [...prev, keys()];
|
|
106
|
-
}, []);
|
|
107
|
-
});
|
|
108
|
-
function createKeyHold(key, options = {}) {
|
|
109
|
-
if (web.isServer) {
|
|
110
|
-
return () => false;
|
|
111
|
-
}
|
|
112
|
-
key = key.toUpperCase();
|
|
113
|
-
const { preventDefault = true } = options, event = exports.useKeyDownEvent(), heldKey = exports.useCurrentlyHeldKey();
|
|
114
|
-
return solidJs.createMemo(() => heldKey() === key && (preventDefault && event()?.preventDefault(), true));
|
|
115
|
-
}
|
|
116
|
-
function createShortcut(keys, callback, options = {}) {
|
|
117
|
-
if (web.isServer || !keys.length) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
keys = keys.map((key) => key.toUpperCase());
|
|
121
|
-
const { preventDefault = true } = options, event = exports.useKeyDownEvent(), sequence = exports.useKeyDownSequence();
|
|
122
|
-
let reset = false;
|
|
123
|
-
const handleSequenceWithReset = (sequence2) => {
|
|
124
|
-
if (!sequence2.length)
|
|
125
|
-
return reset = false;
|
|
126
|
-
if (reset)
|
|
127
|
-
return;
|
|
128
|
-
const e = event();
|
|
129
|
-
if (sequence2.length < keys.length) {
|
|
130
|
-
if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) {
|
|
131
|
-
preventDefault && e && e.preventDefault();
|
|
132
|
-
} else {
|
|
133
|
-
reset = true;
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
reset = true;
|
|
137
|
-
if (equalsKeyHoldSequence(sequence2, keys)) {
|
|
138
|
-
preventDefault && e && e.preventDefault();
|
|
139
|
-
callback(e);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
const handleSequenceWithoutReset = (sequence2) => {
|
|
144
|
-
const last = sequence2.at(-1);
|
|
145
|
-
if (!last)
|
|
146
|
-
return;
|
|
147
|
-
const e = event();
|
|
148
|
-
if (preventDefault && last.length < keys.length) {
|
|
149
|
-
if (utils.arrayEquals(last, keys.slice(0, keys.length - 1))) {
|
|
150
|
-
e && e.preventDefault();
|
|
151
|
-
}
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (utils.arrayEquals(last, keys)) {
|
|
155
|
-
const prev = sequence2.at(-2);
|
|
156
|
-
if (!prev || utils.arrayEquals(prev, keys.slice(0, keys.length - 1))) {
|
|
157
|
-
preventDefault && e && e.preventDefault();
|
|
158
|
-
callback(e);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
solidJs.createEffect(
|
|
163
|
-
solidJs.on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset)
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
exports.createKeyHold = createKeyHold;
|
|
168
|
-
exports.createShortcut = createShortcut;
|
package/dist/index.d.cts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { Accessor } from 'solid-js';
|
|
2
|
-
|
|
3
|
-
type ModifierKey = "Alt" | "Control" | "Meta" | "Shift";
|
|
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>;
|
|
34
|
-
/**
|
|
35
|
-
* Provides a signal with the list of currently held keys, ordered from least recent to most recent.
|
|
36
|
-
*
|
|
37
|
-
* This is a [singleton root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot). *(signals and event-listeners are reused across dependents)*
|
|
38
|
-
*
|
|
39
|
-
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownList
|
|
40
|
-
*
|
|
41
|
-
* @returns
|
|
42
|
-
* Returns a signal of a list of keys
|
|
43
|
-
* ```ts
|
|
44
|
-
* Accessor<string[]>
|
|
45
|
-
* ```
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* const keys = useKeyDownList();
|
|
50
|
-
* createEffect(() => {
|
|
51
|
-
* console.log(keys()) // => ["ALT", "CONTROL", "Q", "A"]
|
|
52
|
-
* })
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
declare const useKeyDownList: () => Accessor<string[]>;
|
|
56
|
-
/**
|
|
57
|
-
* Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`.
|
|
58
|
-
*
|
|
59
|
-
* This is a [singleton root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot). *(signals and event-listeners are reused across dependents)*
|
|
60
|
-
*
|
|
61
|
-
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useCurrentlyHeldKey
|
|
62
|
-
*
|
|
63
|
-
* @returns
|
|
64
|
-
* ```ts
|
|
65
|
-
* Accessor<string | null>
|
|
66
|
-
* ```
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```ts
|
|
70
|
-
* const key = useCurrentlyHeldKey();
|
|
71
|
-
* createEffect(() => {
|
|
72
|
-
* console.log(key()) // => "Q" | "ALT" | ... or null
|
|
73
|
-
* })
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
declare const useCurrentlyHeldKey: () => Accessor<string | null>;
|
|
77
|
-
/**
|
|
78
|
-
* Provides a signal with a sequence of currently held keys, as they were pressed down and up.
|
|
79
|
-
*
|
|
80
|
-
* This is a [singleton root primitive](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot). *(signals and event-listeners are reused across dependents)*
|
|
81
|
-
*
|
|
82
|
-
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownSequence
|
|
83
|
-
*
|
|
84
|
-
* @returns
|
|
85
|
-
* ```ts
|
|
86
|
-
* Accessor<string[][]>
|
|
87
|
-
* // [["CONTROL"], ["CONTROL", "Q"], ["CONTROL", "Q", "A"]]
|
|
88
|
-
* ```
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```ts
|
|
92
|
-
* const sequence = useKeyDownSequence();
|
|
93
|
-
* createEffect(() => {
|
|
94
|
-
* console.log(sequence()) // => string[][]
|
|
95
|
-
* })
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
declare const useKeyDownSequence: () => Accessor<string[][]>;
|
|
99
|
-
/**
|
|
100
|
-
* Provides a `boolean` signal indicating if provided {@link key} is currently being held down.
|
|
101
|
-
* Holding multiple keys at the same time will return `false` — holding only the specified one will return `true`.
|
|
102
|
-
*
|
|
103
|
-
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createKeyHold
|
|
104
|
-
*
|
|
105
|
-
* @param key The key to check for.
|
|
106
|
-
* @options The options for the key hold.
|
|
107
|
-
* - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default.
|
|
108
|
-
* @returns
|
|
109
|
-
* ```ts
|
|
110
|
-
* Accessor<boolean>
|
|
111
|
-
* ```
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```ts
|
|
115
|
-
* const isHeld = createKeyHold("ALT");
|
|
116
|
-
* createEffect(() => {
|
|
117
|
-
* console.log(isHeld()) // => boolean
|
|
118
|
-
* })
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
declare function createKeyHold(key: KbdKey, options?: {
|
|
122
|
-
preventDefault?: boolean;
|
|
123
|
-
}): Accessor<boolean>;
|
|
124
|
-
/**
|
|
125
|
-
* Creates a keyboard shotcut observer. The provided {@link callback} will be called when the specified {@link keys} are pressed.
|
|
126
|
-
*
|
|
127
|
-
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createShortcut
|
|
128
|
-
*
|
|
129
|
-
* @param keys The sequence of keys to watch for.
|
|
130
|
-
* @param callback The callback to call when the keys are pressed.
|
|
131
|
-
* @options The options for the shortcut.
|
|
132
|
-
* - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default.
|
|
133
|
-
* - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```ts
|
|
137
|
-
* createShortcut(["CONTROL", "SHIFT", "C"], () => {
|
|
138
|
-
* console.log("Ctrl+Shift+C was pressed");
|
|
139
|
-
* });
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: {
|
|
143
|
-
preventDefault?: boolean;
|
|
144
|
-
requireReset?: boolean;
|
|
145
|
-
}): void;
|
|
146
|
-
|
|
147
|
-
export { KbdKey, ModifierKey, createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownEvent, useKeyDownList, useKeyDownSequence };
|