UNPKG

react-hotkeys-hook

Version:
247 lines (246 loc) 10.3 kB
import { createContext as W, useContext as z, useState as A, useCallback as v, useRef as H, useLayoutEffect as _, useEffect as m } from "react"; import { jsx as q } from "react/jsx-runtime"; const G = ["shift", "alt", "meta", "mod", "ctrl", "control"], O = { esc: "escape", return: "enter", left: "arrowleft", right: "arrowright", up: "arrowup", down: "arrowdown", ShiftLeft: "shift", ShiftRight: "shift", AltLeft: "alt", AltRight: "alt", MetaLeft: "meta", MetaRight: "meta", OSLeft: "meta", OSRight: "meta", ControlLeft: "ctrl", ControlRight: "ctrl" }; function S(t) { return (O[t.trim()] || t.trim()).toLowerCase().replace(/key|digit|numpad/, ""); } function J(t) { return G.includes(t); } function b(t, r = ",") { return t.toLowerCase().split(r); } function R(t, r = "+", n = ">", f = !1, l) { let u = [], c = !1; t.includes(n) ? (c = !0, u = t.toLocaleLowerCase().split(n).map((i) => S(i))) : u = t.toLocaleLowerCase().split(r).map((i) => S(i)); const d = { alt: u.includes("alt"), ctrl: u.includes("ctrl") || u.includes("control"), shift: u.includes("shift"), meta: u.includes("meta"), mod: u.includes("mod"), useKey: f }, a = u.filter((i) => !G.includes(i)); return { ...d, keys: a, description: l, isSequence: c }; } typeof document < "u" && (document.addEventListener("keydown", (t) => { t.code !== void 0 && Q([S(t.code)]); }), document.addEventListener("keyup", (t) => { t.code !== void 0 && U([S(t.code)]); })), typeof window < "u" && (window.addEventListener("blur", () => { E.clear(); }), window.addEventListener("contextmenu", () => { setTimeout(() => { E.clear(); }, 0); })); const E = /* @__PURE__ */ new Set(); function B(t) { return Array.isArray(t); } function ee(t, r = ",") { return (B(t) ? t : t.split(r)).every((f) => E.has(f.trim().toLowerCase())); } function Q(t) { const r = Array.isArray(t) ? t : [t]; E.has("meta") && E.forEach((n) => !J(n) && E.delete(n.toLowerCase())), r.forEach((n) => E.add(n.toLowerCase())); } function U(t) { const r = Array.isArray(t) ? t : [t]; t === "meta" ? E.clear() : r.forEach((n) => E.delete(n.toLowerCase())); } function te(t, r, n) { (typeof n == "function" && n(t, r) || n === !0) && t.preventDefault(); } function re(t, r, n) { return typeof n == "function" ? n(t, r) : n === !0 || n === void 0; } function ne(t) { return V(t, ["input", "textarea", "select"]); } function V(t, r = !1) { const { target: n, composed: f } = t; let l; return ce(n) && f ? l = t.composedPath()[0] && t.composedPath()[0].tagName : l = n && n.tagName, B(r) ? !!(l && r && r.some((u) => u.toLowerCase() === l.toLowerCase())) : !!(l && r && r); } function ce(t) { return !!t.tagName && !t.tagName.startsWith("-") && t.tagName.includes("-"); } function ue(t, r) { return t.length === 0 && r ? (console.warn( 'A hotkey has the "scopes" option set, however no active scopes were found. If you want to use the global scopes feature, you need to wrap your app in a <HotkeysProvider>' ), !0) : r ? t.some((n) => r.includes(n)) || t.includes("*") : !0; } const oe = (t, r, n = !1) => { const { alt: f, meta: l, mod: u, shift: c, ctrl: d, keys: a, useKey: i } = r, { code: w, key: e, ctrlKey: s, metaKey: y, shiftKey: k, altKey: K } = t, h = S(w); if (i && (a == null ? void 0 : a.length) === 1 && a.includes(e)) return !0; if (!(a != null && a.includes(h)) && !["ctrl", "control", "unknown", "meta", "alt", "shift", "os"].includes(h)) return !1; if (!n) { if (f !== K && h !== "alt" || c !== k && h !== "shift") return !1; if (u) { if (!y && !s) return !1; } else if (l !== y && h !== "meta" && h !== "os" || d !== s && h !== "ctrl" && h !== "control") return !1; } return a && a.length === 1 && a.includes(h) ? !0 : a ? ee(a) : !a; }, X = W(void 0), ae = () => z(X); function fe({ addHotkey: t, removeHotkey: r, children: n }) { return /* @__PURE__ */ q(X.Provider, { value: { addHotkey: t, removeHotkey: r }, children: n }); } function N(t, r) { return t && r && typeof t == "object" && typeof r == "object" ? Object.keys(t).length === Object.keys(r).length && // @ts-expect-error TS7053 Object.keys(t).reduce((n, f) => n && N(t[f], r[f]), !0) : t === r; } const Y = W({ hotkeys: [], activeScopes: [], // This array has to be empty instead of containing '*' as default, to check if the provider is set or not toggleScope: () => { }, enableScope: () => { }, disableScope: () => { } }), le = () => z(Y), he = ({ initiallyActiveScopes: t = ["*"], children: r }) => { const [n, f] = A(t), [l, u] = A([]), c = v((e) => { f((s) => s.includes("*") ? [e] : Array.from(/* @__PURE__ */ new Set([...s, e]))); }, []), d = v((e) => { f((s) => s.filter((y) => y !== e)); }, []), a = v((e) => { f((s) => s.includes(e) ? s.filter((y) => y !== e) : s.includes("*") ? [e] : Array.from(/* @__PURE__ */ new Set([...s, e]))); }, []), i = v((e) => { u((s) => [...s, e]); }, []), w = v((e) => { u((s) => s.filter((y) => !N(y, e))); }, []); return /* @__PURE__ */ q( Y.Provider, { value: { activeScopes: n, hotkeys: l, enableScope: c, disableScope: d, toggleScope: a }, children: /* @__PURE__ */ q(fe, { addHotkey: i, removeHotkey: w, children: r }) } ); }; function se(t) { const r = H(void 0); return N(r.current, t) || (r.current = t), r.current; } const F = (t) => { t.stopPropagation(), t.preventDefault(), t.stopImmediatePropagation(); }, ie = typeof window < "u" ? _ : m; function we(t, r, n, f) { const l = H(null), u = H(!1), c = n instanceof Array ? f instanceof Array ? void 0 : f : n, d = B(t) ? t.join(c == null ? void 0 : c.delimiter) : t, a = n instanceof Array ? n : f instanceof Array ? f : void 0, i = v(r, a ?? []), w = H(i); a ? w.current = i : w.current = r; const e = se(c), { activeScopes: s } = le(), y = ae(); return ie(() => { if ((e == null ? void 0 : e.enabled) === !1 || !ue(s, e == null ? void 0 : e.scopes)) return; let k = [], K; const h = (o, M = !1) => { var j; if (!(ne(o) && !V(o, e == null ? void 0 : e.enableOnFormTags))) { if (l.current !== null) { const L = l.current.getRootNode(); if ((L instanceof Document || L instanceof ShadowRoot) && L.activeElement !== l.current && !l.current.contains(L.activeElement)) { F(o); return; } } (j = o.target) != null && j.isContentEditable && !(e != null && e.enableOnContentEditable) || b(d, e == null ? void 0 : e.delimiter).forEach((L) => { var D, I, p, $; if (L.includes((e == null ? void 0 : e.splitKey) ?? "+") && L.includes((e == null ? void 0 : e.sequenceSplitKey) ?? ">")) { console.warn(`Hotkey ${L} contains both ${(e == null ? void 0 : e.splitKey) ?? "+"} and ${(e == null ? void 0 : e.sequenceSplitKey) ?? ">"} which is not supported.`); return; } const g = R(L, e == null ? void 0 : e.splitKey, e == null ? void 0 : e.sequenceSplitKey, e == null ? void 0 : e.useKey, e == null ? void 0 : e.description); if (g.isSequence) { K = setTimeout(() => { k = []; }, (e == null ? void 0 : e.sequenceTimeoutMs) ?? 1e3); const P = g.useKey ? o.key : S(o.code); if (J(P.toLowerCase())) return; k.push(P); const Z = (D = g.keys) == null ? void 0 : D[k.length - 1]; if (P !== Z) { k = [], K && clearTimeout(K); return; } k.length === ((I = g.keys) == null ? void 0 : I.length) && (w.current(o, g), K && clearTimeout(K), k = []); } else if (oe(o, g, e == null ? void 0 : e.ignoreModifiers) || (p = g.keys) != null && p.includes("*")) { if (($ = e == null ? void 0 : e.ignoreEventWhen) != null && $.call(e, o) || M && u.current) return; if (te(o, g, e == null ? void 0 : e.preventDefault), !re(o, g, e == null ? void 0 : e.enabled)) { F(o); return; } w.current(o, g), M || (u.current = !0); } }); } }, T = (o) => { o.code !== void 0 && (Q(S(o.code)), ((e == null ? void 0 : e.keydown) === void 0 && (e == null ? void 0 : e.keyup) !== !0 || e != null && e.keydown) && h(o)); }, x = (o) => { o.code !== void 0 && (U(S(o.code)), u.current = !1, e != null && e.keyup && h(o, !0)); }, C = l.current || (c == null ? void 0 : c.document) || document; return C.addEventListener("keyup", x, c == null ? void 0 : c.eventListenerOptions), C.addEventListener("keydown", T, c == null ? void 0 : c.eventListenerOptions), y && b(d, e == null ? void 0 : e.delimiter).forEach( (o) => y.addHotkey( R(o, e == null ? void 0 : e.splitKey, e == null ? void 0 : e.sequenceSplitKey, e == null ? void 0 : e.useKey, e == null ? void 0 : e.description) ) ), () => { C.removeEventListener("keyup", x, c == null ? void 0 : c.eventListenerOptions), C.removeEventListener("keydown", T, c == null ? void 0 : c.eventListenerOptions), y && b(d, e == null ? void 0 : e.delimiter).forEach( (o) => y.removeHotkey( R(o, e == null ? void 0 : e.splitKey, e == null ? void 0 : e.sequenceSplitKey, e == null ? void 0 : e.useKey, e == null ? void 0 : e.description) ) ), k = [], K && clearTimeout(K); }; }, [d, e, s]), l; } function ge(t = !1) { const [r, n] = A(/* @__PURE__ */ new Set()), [f, l] = A(!1), u = v((i) => { i.code !== void 0 && (i.preventDefault(), i.stopPropagation(), n((w) => { const e = new Set(w); return e.add(S(t ? i.key : i.code)), e; })); }, [t]), c = v(() => { typeof document < "u" && (document.removeEventListener("keydown", u), l(!1)); }, [u]), d = v(() => { n(/* @__PURE__ */ new Set()), typeof document < "u" && (c(), document.addEventListener("keydown", u), l(!0)); }, [u, c]), a = v(() => { n(/* @__PURE__ */ new Set()); }, []); return [r, { start: d, stop: c, resetKeys: a, isRecording: f }]; } export { he as HotkeysProvider, ee as isHotkeyPressed, we as useHotkeys, le as useHotkeysContext, ge as useRecordHotkeys };