@solid-primitives/keyboard
Advanced tools
Comparing version 1.2.8 to 1.3.0
@@ -1,5 +0,4 @@ | ||
import { Accessor } from 'solid-js'; | ||
type ModifierKey = "Alt" | "Control" | "Meta" | "Shift"; | ||
type KbdKey = ModifierKey | (string & {}); | ||
import { Accessor } from "solid-js"; | ||
export type ModifierKey = "Alt" | "Control" | "Meta" | "Shift"; | ||
export type KbdKey = ModifierKey | (string & {}); | ||
/** | ||
@@ -33,3 +32,3 @@ * Provides a signal with the last keydown event. | ||
*/ | ||
declare const useKeyDownEvent: () => Accessor<KeyboardEvent | null>; | ||
export declare const useKeyDownEvent: () => Accessor<KeyboardEvent | null>; | ||
/** | ||
@@ -56,3 +55,3 @@ * Provides a signal with the list of currently held keys, ordered from least recent to most recent. | ||
*/ | ||
declare const useKeyDownList: () => Accessor<string[]>; | ||
export declare const useKeyDownList: () => Accessor<string[]>; | ||
/** | ||
@@ -78,3 +77,3 @@ * Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`. | ||
*/ | ||
declare const useCurrentlyHeldKey: () => Accessor<string | null>; | ||
export declare const useCurrentlyHeldKey: () => Accessor<string | null>; | ||
/** | ||
@@ -101,3 +100,3 @@ * Provides a signal with a sequence of currently held keys, as they were pressed down and up. | ||
*/ | ||
declare const useKeyDownSequence: () => Accessor<string[][]>; | ||
export declare const useKeyDownSequence: () => Accessor<string[][]>; | ||
/** | ||
@@ -125,3 +124,3 @@ * Provides a `boolean` signal indicating if provided {@link key} is currently being held down. | ||
*/ | ||
declare function createKeyHold(key: KbdKey, options?: { | ||
export declare function createKeyHold(key: KbdKey, options?: { | ||
preventDefault?: boolean; | ||
@@ -147,7 +146,5 @@ }): Accessor<boolean>; | ||
*/ | ||
declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: { | ||
export declare function createShortcut(keys: KbdKey[], callback: (event: KeyboardEvent | null) => void, options?: { | ||
preventDefault?: boolean; | ||
requireReset?: boolean; | ||
}): void; | ||
export { KbdKey, ModifierKey, createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownEvent, useKeyDownList, useKeyDownSequence }; |
@@ -1,82 +0,158 @@ | ||
import { makeEventListener } from '@solid-primitives/event-listener'; | ||
import { createSingletonRoot } from '@solid-primitives/rootless'; | ||
import { arrayEquals } from '@solid-primitives/utils'; | ||
import { createSignal, untrack, createMemo, createEffect, on } from 'solid-js'; | ||
import { isServer } from 'solid-js/web'; | ||
// src/index.ts | ||
import { makeEventListener } from "@solid-primitives/event-listener"; | ||
import { createSingletonRoot } from "@solid-primitives/rootless"; | ||
import { arrayEquals } from "@solid-primitives/utils"; | ||
import { createEffect, createMemo, createSignal, on, untrack } from "solid-js"; | ||
import { isServer } from "solid-js/web"; | ||
function equalsKeyHoldSequence(sequence, model) { | ||
for (let i = sequence.length - 1; i >= 0; i--) { | ||
const _model = model.slice(0, i + 1); | ||
if (!arrayEquals(sequence[i], _model)) | ||
return false; | ||
} | ||
return true; | ||
for (let i = sequence.length - 1; i >= 0; i--) { | ||
const _model = model.slice(0, i + 1); | ||
if (!arrayEquals(sequence[i], _model)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
var useKeyDownEvent = /* @__PURE__ */ createSingletonRoot( | ||
() => { | ||
/** | ||
* Provides a signal with the last keydown event. | ||
* | ||
* The signal is `null` initially, and is reset to that after a timeout. | ||
* | ||
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownEvent | ||
* | ||
* @returns | ||
* Returns a signal of the last keydown event | ||
* ```ts | ||
* Accessor<KeyboardEvent | null> | ||
* ``` | ||
* | ||
* @example | ||
* ```ts | ||
* const event = useKeyDownEvent(); | ||
* | ||
* createEffect(() => { | ||
* const e = event(); | ||
* console.log(e) // => KeyboardEvent | null | ||
* | ||
* if (e) { | ||
* console.log(e.key) // => "Q" | "ALT" | ... or null | ||
* e.preventDefault(); // prevent default behavior or last keydown event | ||
* } | ||
* }) | ||
* ``` | ||
*/ | ||
export const useKeyDownEvent = /*#__PURE__*/ createSingletonRoot(() => { | ||
if (isServer) { | ||
return () => null; | ||
return () => null; | ||
} | ||
const [event, setEvent] = createSignal(null); | ||
makeEventListener(window, "keydown", (e) => { | ||
setEvent(e); | ||
setTimeout(() => setEvent(null)); | ||
makeEventListener(window, "keydown", e => { | ||
setEvent(e); | ||
setTimeout(() => setEvent(null)); | ||
}); | ||
return event; | ||
} | ||
); | ||
var useKeyDownList = /* @__PURE__ */ createSingletonRoot(() => { | ||
if (isServer) { | ||
const keys = () => []; | ||
keys[0] = keys; | ||
keys[1] = { event: () => null }; | ||
keys[Symbol.iterator] = function* () { | ||
yield keys[0]; | ||
yield keys[1]; | ||
}); | ||
/** | ||
* Provides a signal with the list of currently held keys, ordered from least recent to most recent. | ||
* | ||
* 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)* | ||
* | ||
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownList | ||
* | ||
* @returns | ||
* Returns a signal of a list of keys | ||
* ```ts | ||
* Accessor<string[]> | ||
* ``` | ||
* | ||
* @example | ||
* ```ts | ||
* const keys = useKeyDownList(); | ||
* createEffect(() => { | ||
* console.log(keys()) // => ["ALT", "CONTROL", "Q", "A"] | ||
* }) | ||
* ``` | ||
*/ | ||
export const useKeyDownList = /*#__PURE__*/ createSingletonRoot(() => { | ||
if (isServer) { | ||
const keys = () => []; | ||
// this is for backwards compatibility | ||
// TODO remove in the next major version | ||
keys[0] = keys; | ||
keys[1] = { event: () => null }; | ||
keys[Symbol.iterator] = function* () { | ||
yield keys[0]; | ||
yield keys[1]; | ||
}; | ||
return keys; | ||
} | ||
const [pressedKeys, setPressedKeys] = createSignal([]), reset = () => setPressedKeys([]), event = useKeyDownEvent(); | ||
makeEventListener(window, "keydown", e => { | ||
// e.key may be undefined when used with <datalist> el | ||
// gh issue: https://github.com/solidjs-community/solid-primitives/issues/246 | ||
if (e.repeat || typeof e.key !== "string") | ||
return; | ||
const key = e.key.toUpperCase(), currentKeys = pressedKeys(); | ||
if (currentKeys.includes(key)) | ||
return; | ||
const keys = [...currentKeys, key]; | ||
// if the modifier is pressed before we start listening | ||
// we should add it to the list | ||
if (currentKeys.length === 0 && | ||
key !== "ALT" && | ||
key !== "CONTROL" && | ||
key !== "META" && | ||
key !== "SHIFT") { | ||
if (e.shiftKey) | ||
keys.unshift("SHIFT"); | ||
if (e.altKey) | ||
keys.unshift("ALT"); | ||
if (e.ctrlKey) | ||
keys.unshift("CONTROL"); | ||
if (e.metaKey) | ||
keys.unshift("META"); | ||
} | ||
setPressedKeys(keys); | ||
}); | ||
makeEventListener(window, "keyup", e => { | ||
if (typeof e.key !== "string") | ||
return; | ||
const key = e.key.toUpperCase(); | ||
setPressedKeys(prev => prev.filter(_key => _key !== key)); | ||
}); | ||
makeEventListener(window, "blur", reset); | ||
makeEventListener(window, "contextmenu", e => { | ||
e.defaultPrevented || reset(); | ||
}); | ||
// this is for backwards compatibility | ||
// TODO remove in the next major version | ||
pressedKeys[0] = pressedKeys; | ||
pressedKeys[1] = { event }; | ||
pressedKeys[Symbol.iterator] = function* () { | ||
yield pressedKeys[0]; | ||
yield pressedKeys[1]; | ||
}; | ||
return keys; | ||
} | ||
const [pressedKeys, setPressedKeys] = createSignal([]), reset = () => setPressedKeys([]), event = useKeyDownEvent(); | ||
makeEventListener(window, "keydown", (e) => { | ||
if (e.repeat || typeof e.key !== "string") | ||
return; | ||
const key = e.key.toUpperCase(), currentKeys = pressedKeys(); | ||
if (currentKeys.includes(key)) | ||
return; | ||
const keys = [...currentKeys, key]; | ||
if (currentKeys.length === 0 && key !== "ALT" && key !== "CONTROL" && key !== "META" && key !== "SHIFT") { | ||
if (e.shiftKey) | ||
keys.unshift("SHIFT"); | ||
if (e.altKey) | ||
keys.unshift("ALT"); | ||
if (e.ctrlKey) | ||
keys.unshift("CONTROL"); | ||
if (e.metaKey) | ||
keys.unshift("META"); | ||
} | ||
setPressedKeys(keys); | ||
}); | ||
makeEventListener(window, "keyup", (e) => { | ||
if (typeof e.key !== "string") | ||
return; | ||
const key = e.key.toUpperCase(); | ||
setPressedKeys((prev) => prev.filter((_key) => _key !== key)); | ||
}); | ||
makeEventListener(window, "blur", reset); | ||
makeEventListener(window, "contextmenu", (e) => { | ||
e.defaultPrevented || reset(); | ||
}); | ||
pressedKeys[0] = pressedKeys; | ||
pressedKeys[1] = { event }; | ||
pressedKeys[Symbol.iterator] = function* () { | ||
yield pressedKeys[0]; | ||
yield pressedKeys[1]; | ||
}; | ||
return pressedKeys; | ||
return pressedKeys; | ||
}); | ||
var useCurrentlyHeldKey = /* @__PURE__ */ createSingletonRoot( | ||
() => { | ||
/** | ||
* Provides a signal with the currently held single key. Pressing any other key at the same time will reset the signal to `null`. | ||
* | ||
* 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)* | ||
* | ||
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useCurrentlyHeldKey | ||
* | ||
* @returns | ||
* ```ts | ||
* Accessor<string | null> | ||
* ``` | ||
* | ||
* @example | ||
* ```ts | ||
* const key = useCurrentlyHeldKey(); | ||
* createEffect(() => { | ||
* console.log(key()) // => "Q" | "ALT" | ... or null | ||
* }) | ||
* ``` | ||
*/ | ||
export const useCurrentlyHeldKey = /*#__PURE__*/ createSingletonRoot(() => { | ||
if (isServer) { | ||
return () => null; | ||
return () => null; | ||
} | ||
@@ -86,81 +162,143 @@ const keys = useKeyDownList(); | ||
return createMemo(() => { | ||
const _keys = keys(); | ||
const prev = prevKeys; | ||
prevKeys = _keys; | ||
if (prev.length === 0 && _keys.length === 1) | ||
return _keys[0]; | ||
return null; | ||
const _keys = keys(); | ||
const prev = prevKeys; | ||
prevKeys = _keys; | ||
if (prev.length === 0 && _keys.length === 1) | ||
return _keys[0]; | ||
return null; | ||
}); | ||
} | ||
); | ||
var useKeyDownSequence = /* @__PURE__ */ createSingletonRoot(() => { | ||
if (isServer) { | ||
return () => []; | ||
} | ||
const keys = useKeyDownList(); | ||
return createMemo((prev) => { | ||
if (keys().length === 0) | ||
return []; | ||
return [...prev, keys()]; | ||
}, []); | ||
}); | ||
function createKeyHold(key, options = {}) { | ||
if (isServer) { | ||
return () => false; | ||
} | ||
key = key.toUpperCase(); | ||
const { preventDefault = true } = options, event = useKeyDownEvent(), heldKey = useCurrentlyHeldKey(); | ||
return createMemo(() => heldKey() === key && (preventDefault && event()?.preventDefault(), true)); | ||
} | ||
function createShortcut(keys, callback, options = {}) { | ||
if (isServer || !keys.length) { | ||
return; | ||
} | ||
keys = keys.map((key) => key.toUpperCase()); | ||
const { preventDefault = true } = options, event = useKeyDownEvent(), sequence = useKeyDownSequence(); | ||
let reset = false; | ||
const handleSequenceWithReset = (sequence2) => { | ||
if (!sequence2.length) | ||
return reset = false; | ||
if (reset) | ||
return; | ||
const e = event(); | ||
if (sequence2.length < keys.length) { | ||
if (equalsKeyHoldSequence(sequence2, keys.slice(0, sequence2.length))) { | ||
preventDefault && e && e.preventDefault(); | ||
} else { | ||
reset = true; | ||
} | ||
} else { | ||
reset = true; | ||
if (equalsKeyHoldSequence(sequence2, keys)) { | ||
preventDefault && e && e.preventDefault(); | ||
callback(e); | ||
} | ||
/** | ||
* Provides a signal with a sequence of currently held keys, as they were pressed down and up. | ||
* | ||
* 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)* | ||
* | ||
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#useKeyDownSequence | ||
* | ||
* @returns | ||
* ```ts | ||
* Accessor<string[][]> | ||
* // [["CONTROL"], ["CONTROL", "Q"], ["CONTROL", "Q", "A"]] | ||
* ``` | ||
* | ||
* @example | ||
* ```ts | ||
* const sequence = useKeyDownSequence(); | ||
* createEffect(() => { | ||
* console.log(sequence()) // => string[][] | ||
* }) | ||
* ``` | ||
*/ | ||
export const useKeyDownSequence = /*#__PURE__*/ createSingletonRoot(() => { | ||
if (isServer) { | ||
return () => []; | ||
} | ||
}; | ||
const handleSequenceWithoutReset = (sequence2) => { | ||
const last = sequence2.at(-1); | ||
if (!last) | ||
return; | ||
const e = event(); | ||
if (preventDefault && last.length < keys.length) { | ||
if (arrayEquals(last, keys.slice(0, keys.length - 1))) { | ||
e && e.preventDefault(); | ||
} | ||
return; | ||
const keys = useKeyDownList(); | ||
return createMemo(prev => { | ||
if (keys().length === 0) | ||
return []; | ||
return [...prev, keys()]; | ||
}, []); | ||
}); | ||
/** | ||
* Provides a `boolean` signal indicating if provided {@link key} is currently being held down. | ||
* Holding multiple keys at the same time will return `false` — holding only the specified one will return `true`. | ||
* | ||
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createKeyHold | ||
* | ||
* @param key The key to check for. | ||
* @options The options for the key hold. | ||
* - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default. | ||
* @returns | ||
* ```ts | ||
* Accessor<boolean> | ||
* ``` | ||
* | ||
* @example | ||
* ```ts | ||
* const isHeld = createKeyHold("ALT"); | ||
* createEffect(() => { | ||
* console.log(isHeld()) // => boolean | ||
* }) | ||
* ``` | ||
*/ | ||
export function createKeyHold(key, options = {}) { | ||
if (isServer) { | ||
return () => false; | ||
} | ||
if (arrayEquals(last, keys)) { | ||
const prev = sequence2.at(-2); | ||
if (!prev || arrayEquals(prev, keys.slice(0, keys.length - 1))) { | ||
preventDefault && e && e.preventDefault(); | ||
callback(e); | ||
} | ||
key = key.toUpperCase(); | ||
const { preventDefault = true } = options, event = useKeyDownEvent(), heldKey = useCurrentlyHeldKey(); | ||
return createMemo(() => heldKey() === key && (preventDefault && event()?.preventDefault(), true)); | ||
} | ||
/** | ||
* Creates a keyboard shotcut observer. The provided {@link callback} will be called when the specified {@link keys} are pressed. | ||
* | ||
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyboard#createShortcut | ||
* | ||
* @param keys The sequence of keys to watch for. | ||
* @param callback The callback to call when the keys are pressed. | ||
* @options The options for the shortcut. | ||
* - `preventDefault` — Controlls in the keydown event should have it's default action prevented. Enabled by default. | ||
* - `requireReset` — If `true`, the shortcut will only be triggered once until all of the keys stop being pressed. Disabled by default. | ||
* | ||
* @example | ||
* ```ts | ||
* createShortcut(["CONTROL", "SHIFT", "C"], () => { | ||
* console.log("Ctrl+Shift+C was pressed"); | ||
* }); | ||
* ``` | ||
*/ | ||
export function createShortcut(keys, callback, options = {}) { | ||
if (isServer || !keys.length) { | ||
return; | ||
} | ||
}; | ||
createEffect( | ||
on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset) | ||
); | ||
keys = keys.map(key => key.toUpperCase()); | ||
const { preventDefault = true } = options, event = useKeyDownEvent(), sequence = useKeyDownSequence(); | ||
let reset = false; | ||
// allow to check the sequence only once the user has released all keys | ||
const handleSequenceWithReset = (sequence) => { | ||
if (!sequence.length) | ||
return (reset = false); | ||
if (reset) | ||
return; | ||
const e = event(); | ||
if (sequence.length < keys.length) { | ||
// optimistically preventDefault behavior if we yet don't have enough keys | ||
if (equalsKeyHoldSequence(sequence, keys.slice(0, sequence.length))) { | ||
preventDefault && e && e.preventDefault(); | ||
} | ||
else { | ||
reset = true; | ||
} | ||
} | ||
else { | ||
reset = true; | ||
if (equalsKeyHoldSequence(sequence, keys)) { | ||
preventDefault && e && e.preventDefault(); | ||
callback(e); | ||
} | ||
} | ||
}; | ||
// allow checking the sequence even if the user is still holding down keys | ||
const handleSequenceWithoutReset = (sequence) => { | ||
const last = sequence.at(-1); | ||
if (!last) | ||
return; | ||
const e = event(); | ||
// optimistically preventDefault behavior if we yet don't have enough keys | ||
if (preventDefault && last.length < keys.length) { | ||
if (arrayEquals(last, keys.slice(0, keys.length - 1))) { | ||
e && e.preventDefault(); | ||
} | ||
return; | ||
} | ||
if (arrayEquals(last, keys)) { | ||
const prev = sequence.at(-2); | ||
if (!prev || arrayEquals(prev, keys.slice(0, keys.length - 1))) { | ||
preventDefault && e && e.preventDefault(); | ||
callback(e); | ||
} | ||
} | ||
}; | ||
createEffect(on(sequence, options.requireReset ? handleSequenceWithReset : handleSequenceWithoutReset)); | ||
} | ||
export { createKeyHold, createShortcut, useCurrentlyHeldKey, useKeyDownEvent, useKeyDownList, useKeyDownSequence }; |
{ | ||
"name": "@solid-primitives/keyboard", | ||
"version": "1.2.8", | ||
"version": "1.3.0", | ||
"description": "A library of reactive promitives helping handling user's keyboard input.", | ||
@@ -41,3 +41,2 @@ "author": "Damian Tarnwski <gthetarnav@gmail.com>", | ||
"type": "module", | ||
"main": "./dist/index.cjs", | ||
"module": "./dist/index.js", | ||
@@ -47,15 +46,12 @@ "types": "./dist/index.d.ts", | ||
"exports": { | ||
"@solid-primitives/source": "./src/index.ts", | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
}, | ||
"dependencies": { | ||
"@solid-primitives/event-listener": "^2.3.3", | ||
"@solid-primitives/utils": "^6.2.3", | ||
"@solid-primitives/rootless": "^1.4.5" | ||
"@solid-primitives/event-listener": "^2.4.0", | ||
"@solid-primitives/rootless": "^1.5.0", | ||
"@solid-primitives/utils": "^6.3.0" | ||
}, | ||
@@ -62,0 +58,0 @@ "peerDependencies": { |
@@ -14,8 +14,8 @@ <p> | ||
- [`useKeyDownEvent`](#useKeyDownEvent) — Provides a signal with the last keydown event. | ||
- [`useKeyDownList`](#useKeyDownList) — Provides a signal with the list of currently held keys | ||
- [`useCurrentlyHeldKey`](#useCurrentlyHeldKey) — Provides a signal with the currently held single key. | ||
- [`useKeyDownSequence`](#useKeyDownSequence) — Provides a signal with a sequence of currently held keys, as they were pressed down and up. | ||
- [`createKeyHold`](#createKeyHold) — Provides a signal indicating if provided key is currently being held down. | ||
- [`createShortcut`](#createShortcut) — Creates a keyboard shotcut observer. | ||
- [`useKeyDownEvent`](#usekeydownevent) — Provides a signal with the last keydown event. | ||
- [`useKeyDownList`](#usekeydownlist) — Provides a signal with the list of currently held keys | ||
- [`useCurrentlyHeldKey`](#usecurrentlyheldkey) — Provides a signal with the currently held single key. | ||
- [`useKeyDownSequence`](#usekeydownsequence) — Provides a signal with a sequence of currently held keys, as they were pressed down and up. | ||
- [`createKeyHold`](#createkeyhold) — Provides a signal indicating if provided key is currently being held down. | ||
- [`createShortcut`](#createshortcut) — Creates a keyboard shotcut observer. | ||
@@ -22,0 +22,0 @@ ## Installation |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
25000
5
447
1