@solid-primitives/keyboard 2.0.0-next.0 → 2.0.0-next.2

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
@@ -156,6 +156,7 @@ Creates a keyboard shortcut observer. The provided callback will be called when
156
156
  - `options` — additional configuration:
157
157
  - `preventDefault` — call `e.preventDefault()` on the keyboard event, when the specified key is pressed. _(Defaults to `true`)_
158
158
  - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
159
+ - `ignoreWithinInputs` — If `true`, the shortcut is ignored while focus is on an `input`, `textarea`, `select`, or `contenteditable` element, so it doesn't interrupt typing. Disabled by default.
159
160
 
160
161
  ```tsx
161
162
  import { createShortcut } from "@solid-primitives/keyboard";
@@ -175,6 +176,17 @@ When `preventDefault` is `true`, `e.preventDefault()` will be called not only on
175
176
 
176
177
  E.g. when listening for `Control + Shift + A`, all three keydown events will be prevented.
177
178
 
179
+ ### Ignoring shortcuts while typing
180
+
181
+ Single, unmodified-key shortcuts (e.g. `["S"]`) conflict with typing — pressing "s" in a text field would both type the character and trigger the shortcut. Set `ignoreWithinInputs: true` to skip the shortcut entirely while focus is on a form control or `contenteditable` element:
182
+
183
+ ```tsx
184
+ // won't fire while the user is typing in a text field
185
+ createShortcut(["S"], () => console.log("S was pressed"), { ignoreWithinInputs: true });
186
+ ```
187
+
188
+ Combos that include a modifier (e.g. `Control + S`) don't have this problem, since the modifier itself prevents a character from being typed — `ignoreWithinInputs` is usually unnecessary for those.
189
+
178
190
  ## `createKeyDown`
179
191
 
180
192
  Listens for a `keydown` event for a specific key on a document. Useful for global keyboard handlers that need to respond to a single key without setting up a full shortcut.
package/dist/index.d.ts CHANGED
@@ -131,17 +131,25 @@ export declare function createKeyHold(key: KbdKey, options?: {
131
131
  * @param options The options for the shortcut.
132
132
  * - `preventDefault` — Controls if the keydown event should have its default action prevented. Enabled by default.
133
133
  * - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
134
+ * - `ignoreWithinInputs` — If `true`, the shortcut is ignored while focus is on an `input`, `textarea`, `select`,
135
+ * or `contenteditable` element, so typing isn't interrupted. Disabled by default — enable it for shortcuts
136
+ * made of plain, unmodified keys (e.g. a single letter); combos like `Control+S` are usually fine either way,
137
+ * since the modifier prevents a character from being typed.
134
138
  *
135
139
  * @example
136
140
  * ```ts
137
141
  * createShortcut(["CONTROL", "SHIFT", "C"], () => {
138
142
  * console.log("Ctrl+Shift+C was pressed");
139
143
  * });
144
+ *
145
+ * // won't fire while typing in a text field
146
+ * createShortcut(["S"], () => console.log("S was pressed"), { ignoreWithinInputs: true });
140
147
  * ```
141
148
  */
142
149
  export declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: {
143
150
  preventDefault?: boolean;
144
151
  requireReset?: boolean;
152
+ ignoreWithinInputs?: boolean;
145
153
  }): void;
146
154
  export interface CreateKeyDownOptions {
147
155
  /** Whether the listener should be inactive. */
package/dist/index.js CHANGED
@@ -11,6 +11,19 @@ function equalsKeyHoldSequence(sequence, model) {
11
11
  }
12
12
  return true;
13
13
  }
14
+ /** Is the event target an editable form control, or inside a `contenteditable` element? */
15
+ function isEditableTarget(target) {
16
+ if (!(target instanceof HTMLElement))
17
+ return false;
18
+ switch (target.tagName) {
19
+ case "INPUT":
20
+ case "TEXTAREA":
21
+ case "SELECT":
22
+ return true;
23
+ default:
24
+ return target.isContentEditable;
25
+ }
26
+ }
14
27
  /**
15
28
  * Provides a signal with the last keydown event.
16
29
  *
@@ -108,7 +121,16 @@ export const useKeyDownList = /*#__PURE__*/ createSingletonRoot(() => {
108
121
  if (typeof e.key !== "string")
109
122
  return;
110
123
  const key = e.key.toUpperCase();
111
- setPressedKeys(prev => prev.filter(_key => _key !== key));
124
+ // macOS never fires keyup for other keys held down together with Meta —
125
+ // only Meta's own keyup arrives — so its release must clear everything,
126
+ // or the other keys' stale state corrupts the next press.
127
+ // See https://github.com/solidjs-community/solid-primitives/issues/269
128
+ if (key === "META") {
129
+ reset();
130
+ }
131
+ else {
132
+ setPressedKeys(prev => prev.filter(_key => _key !== key));
133
+ }
112
134
  });
113
135
  makeEventListener(window, "blur", reset);
114
136
  makeEventListener(window, "contextmenu", e => {
@@ -235,12 +257,19 @@ export function createKeyHold(key, options = {}) {
235
257
  * @param options The options for the shortcut.
236
258
  * - `preventDefault` — Controls if the keydown event should have its default action prevented. Enabled by default.
237
259
  * - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default.
260
+ * - `ignoreWithinInputs` — If `true`, the shortcut is ignored while focus is on an `input`, `textarea`, `select`,
261
+ * or `contenteditable` element, so typing isn't interrupted. Disabled by default — enable it for shortcuts
262
+ * made of plain, unmodified keys (e.g. a single letter); combos like `Control+S` are usually fine either way,
263
+ * since the modifier prevents a character from being typed.
238
264
  *
239
265
  * @example
240
266
  * ```ts
241
267
  * createShortcut(["CONTROL", "SHIFT", "C"], () => {
242
268
  * console.log("Ctrl+Shift+C was pressed");
243
269
  * });
270
+ *
271
+ * // won't fire while typing in a text field
272
+ * createShortcut(["S"], () => console.log("S was pressed"), { ignoreWithinInputs: true });
244
273
  * ```
245
274
  */
246
275
  export function createShortcut(keys, callback, options = {}) {
@@ -248,7 +277,7 @@ export function createShortcut(keys, callback, options = {}) {
248
277
  return;
249
278
  }
250
279
  keys = keys.map(key => key.toUpperCase());
251
- const { preventDefault = true, requireReset = false } = options;
280
+ const { preventDefault = true, requireReset = false, ignoreWithinInputs = false } = options;
252
281
  // Track pressed keys and sequence locally with plain JS state rather than
253
282
  // reactive signals. A signal reads from event listeners return
254
283
  // the pre-batch committed value, so synchronous shortcut checking requires
@@ -264,6 +293,8 @@ export function createShortcut(keys, callback, options = {}) {
264
293
  makeEventListener(window, "keydown", (e) => {
265
294
  if (e.repeat || typeof e.key !== "string")
266
295
  return;
296
+ if (ignoreWithinInputs && isEditableTarget(e.target))
297
+ return;
267
298
  const key = e.key.toUpperCase();
268
299
  if (!pressedKeys.includes(key)) {
269
300
  const newKeys = [...pressedKeys];
@@ -328,6 +359,14 @@ export function createShortcut(keys, callback, options = {}) {
328
359
  if (typeof e.key !== "string")
329
360
  return;
330
361
  const key = e.key.toUpperCase();
362
+ // macOS never fires keyup for other keys held down together with Meta —
363
+ // only Meta's own keyup arrives. Treat it as a signal that every other
364
+ // tracked key must have been released too, or their stale state corrupts
365
+ // the next press (see https://github.com/solidjs-community/solid-primitives/issues/269).
366
+ if (key === "META") {
367
+ resetAll();
368
+ return;
369
+ }
331
370
  pressedKeys = pressedKeys.filter(k => k !== key);
332
371
  if (pressedKeys.length === 0) {
333
372
  sequence = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solid-primitives/keyboard",
3
- "version": "2.0.0-next.0",
3
+ "version": "2.0.0-next.2",
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": [],