@develemit/web-hooks 0.1.0 → 0.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +166 -0
  3. package/dist/auto-grow.cjs +60 -0
  4. package/dist/auto-grow.d.cts +14 -0
  5. package/dist/auto-grow.d.ts +14 -0
  6. package/dist/auto-grow.js +9 -0
  7. package/dist/chunk-3CACI4OQ.js +68 -0
  8. package/dist/chunk-3UGRXQIA.js +39 -0
  9. package/dist/chunk-5CA5RIMH.js +34 -0
  10. package/dist/chunk-5KIH2XLV.js +136 -0
  11. package/dist/chunk-6KCRVSLS.js +80 -0
  12. package/dist/chunk-DNU57XEJ.js +82 -0
  13. package/dist/chunk-IWQDAJEP.js +69 -0
  14. package/dist/chunk-LZKAGDER.js +22 -0
  15. package/dist/chunk-NKLEB5TR.js +98 -0
  16. package/dist/chunk-OZ4PU4E3.js +115 -0
  17. package/dist/chunk-S4YKMGQE.js +23 -0
  18. package/dist/chunk-UPVNSGIK.js +72 -0
  19. package/dist/chunk-UZADAINO.js +61 -0
  20. package/dist/clipboard.cjs +5 -0
  21. package/dist/clipboard.js +4 -57
  22. package/dist/debounce.cjs +96 -0
  23. package/dist/debounce.d.cts +21 -0
  24. package/dist/debounce.d.ts +21 -0
  25. package/dist/debounce.js +11 -0
  26. package/dist/drawer-swipe.js +12 -101
  27. package/dist/escape.cjs +1 -0
  28. package/dist/escape.js +3 -17
  29. package/dist/focus-trap.cjs +3 -1
  30. package/dist/focus-trap.js +6 -72
  31. package/dist/hotkey.cjs +127 -0
  32. package/dist/hotkey.d.cts +31 -0
  33. package/dist/hotkey.d.ts +31 -0
  34. package/dist/hotkey.js +15 -0
  35. package/dist/index.cjs +613 -3
  36. package/dist/index.d.cts +14 -60
  37. package/dist/index.d.ts +14 -60
  38. package/dist/index.js +97 -247
  39. package/dist/media-query.cjs +90 -0
  40. package/dist/media-query.d.cts +12 -0
  41. package/dist/media-query.d.ts +12 -0
  42. package/dist/media-query.js +15 -0
  43. package/dist/outside-press.cjs +66 -0
  44. package/dist/outside-press.d.cts +15 -0
  45. package/dist/outside-press.d.ts +15 -0
  46. package/dist/outside-press.js +11 -0
  47. package/dist/scroll-lock.cjs +107 -0
  48. package/dist/scroll-lock.d.cts +8 -0
  49. package/dist/scroll-lock.d.ts +8 -0
  50. package/dist/scroll-lock.js +11 -0
  51. package/dist/storage-state.cjs +161 -0
  52. package/dist/storage-state.d.cts +14 -0
  53. package/dist/storage-state.d.ts +14 -0
  54. package/dist/storage-state.js +7 -0
  55. package/dist/unsaved-guard.cjs +48 -0
  56. package/dist/unsaved-guard.d.cts +5 -0
  57. package/dist/unsaved-guard.d.ts +5 -0
  58. package/dist/unsaved-guard.js +9 -0
  59. package/dist/visibility-polling.cjs +98 -0
  60. package/dist/visibility-polling.d.cts +14 -0
  61. package/dist/visibility-polling.d.ts +14 -0
  62. package/dist/visibility-polling.js +9 -0
  63. package/package.json +55 -2
@@ -0,0 +1,96 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/debounce/index.ts
22
+ var debounce_exports = {};
23
+ __export(debounce_exports, {
24
+ createDebouncer: () => createDebouncer,
25
+ useDebouncedCallback: () => useDebouncedCallback,
26
+ useDebouncedValue: () => useDebouncedValue
27
+ });
28
+ module.exports = __toCommonJS(debounce_exports);
29
+
30
+ // src/debounce/debouncer.ts
31
+ function createDebouncer(getFn, getMs) {
32
+ let timer;
33
+ let pending;
34
+ function clear() {
35
+ if (timer !== void 0) clearTimeout(timer);
36
+ timer = void 0;
37
+ pending = void 0;
38
+ }
39
+ function run() {
40
+ const args = pending;
41
+ clear();
42
+ if (args) getFn()(...args);
43
+ }
44
+ return {
45
+ call(...args) {
46
+ if (timer !== void 0) clearTimeout(timer);
47
+ pending = args;
48
+ timer = setTimeout(run, getMs());
49
+ },
50
+ cancel: clear,
51
+ flush() {
52
+ if (pending) run();
53
+ },
54
+ isPending: () => pending !== void 0
55
+ };
56
+ }
57
+
58
+ // src/debounce/use-debounced-value.ts
59
+ var import_react = require("react");
60
+ function useDebouncedValue(value, ms) {
61
+ const [debounced, setDebounced] = (0, import_react.useState)(value);
62
+ (0, import_react.useEffect)(() => {
63
+ const timer = setTimeout(() => setDebounced(value), ms);
64
+ return () => clearTimeout(timer);
65
+ }, [value, ms]);
66
+ return debounced;
67
+ }
68
+
69
+ // src/debounce/use-debounced-callback.ts
70
+ var import_react2 = require("react");
71
+ function useDebouncedCallback(fn, ms) {
72
+ const fnRef = (0, import_react2.useRef)(fn);
73
+ const msRef = (0, import_react2.useRef)(ms);
74
+ (0, import_react2.useEffect)(() => {
75
+ fnRef.current = fn;
76
+ msRef.current = ms;
77
+ });
78
+ const [debounced] = (0, import_react2.useState)(() => {
79
+ const debouncer = createDebouncer(
80
+ () => fnRef.current,
81
+ () => msRef.current
82
+ );
83
+ return Object.assign((...args) => debouncer.call(...args), {
84
+ cancel: debouncer.cancel,
85
+ flush: debouncer.flush
86
+ });
87
+ });
88
+ (0, import_react2.useEffect)(() => () => debounced.cancel(), [debounced]);
89
+ return debounced;
90
+ }
91
+ // Annotate the CommonJS export names for ESM import in node:
92
+ 0 && (module.exports = {
93
+ createDebouncer,
94
+ useDebouncedCallback,
95
+ useDebouncedValue
96
+ });
@@ -0,0 +1,21 @@
1
+ interface Debouncer<Args extends unknown[]> {
2
+ call(...args: Args): void;
3
+ cancel(): void;
4
+ /** Runs the pending call right now; does nothing when none is pending. */
5
+ flush(): void;
6
+ isPending(): boolean;
7
+ }
8
+ /** `getFn` and `getMs` are read at call time, so the latest values always win. */
9
+ declare function createDebouncer<Args extends unknown[]>(getFn: () => (...args: Args) => void, getMs: () => number): Debouncer<Args>;
10
+
11
+ declare function useDebouncedValue<T>(value: T, ms: number): T;
12
+
13
+ interface DebouncedCallback<Args extends unknown[]> {
14
+ (...args: Args): void;
15
+ cancel(): void;
16
+ flush(): void;
17
+ }
18
+ /** The returned function keeps one identity for the life of the component. */
19
+ declare function useDebouncedCallback<Args extends unknown[]>(fn: (...args: Args) => void, ms: number): DebouncedCallback<Args>;
20
+
21
+ export { type DebouncedCallback, type Debouncer, createDebouncer, useDebouncedCallback, useDebouncedValue };
@@ -0,0 +1,21 @@
1
+ interface Debouncer<Args extends unknown[]> {
2
+ call(...args: Args): void;
3
+ cancel(): void;
4
+ /** Runs the pending call right now; does nothing when none is pending. */
5
+ flush(): void;
6
+ isPending(): boolean;
7
+ }
8
+ /** `getFn` and `getMs` are read at call time, so the latest values always win. */
9
+ declare function createDebouncer<Args extends unknown[]>(getFn: () => (...args: Args) => void, getMs: () => number): Debouncer<Args>;
10
+
11
+ declare function useDebouncedValue<T>(value: T, ms: number): T;
12
+
13
+ interface DebouncedCallback<Args extends unknown[]> {
14
+ (...args: Args): void;
15
+ cancel(): void;
16
+ flush(): void;
17
+ }
18
+ /** The returned function keeps one identity for the life of the component. */
19
+ declare function useDebouncedCallback<Args extends unknown[]>(fn: (...args: Args) => void, ms: number): DebouncedCallback<Args>;
20
+
21
+ export { type DebouncedCallback, type Debouncer, createDebouncer, useDebouncedCallback, useDebouncedValue };
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import {
3
+ createDebouncer,
4
+ useDebouncedCallback,
5
+ useDebouncedValue
6
+ } from "./chunk-IWQDAJEP.js";
7
+ export {
8
+ createDebouncer,
9
+ useDebouncedCallback,
10
+ useDebouncedValue
11
+ };
@@ -1,105 +1,16 @@
1
1
  "use client";
2
-
3
- // src/drawer-swipe/swipe-intent.ts
4
- var BACK_ZONE_PX = 60;
5
- var FORWARD_ZONE_PX = 30;
6
- var OPEN_DISTANCE_PX = 80;
7
- var CLOSE_DISTANCE_PX = 60;
8
- var VERTICAL_CANCEL_PX = 40;
9
- var MAX_SWIPE_DURATION_MS = 450;
10
- var HORIZONTAL_DOMINANCE = 1.5;
11
- function isInOpenZone(x, viewportWidth) {
12
- return x > BACK_ZONE_PX && x < viewportWidth - FORWARD_ZONE_PX;
13
- }
14
- function resolveSwipeIntent(start, end, drawerOpen, viewportWidth) {
15
- const dx = end.x - start.x;
16
- const dy = end.y - start.y;
17
- if (end.time - start.time > MAX_SWIPE_DURATION_MS) return null;
18
- if (Math.abs(dx) < Math.abs(dy) * HORIZONTAL_DOMINANCE) return null;
19
- if (drawerOpen) {
20
- return dx <= -CLOSE_DISTANCE_PX ? "close" : null;
21
- }
22
- if (dx < OPEN_DISTANCE_PX) return null;
23
- return isInOpenZone(start.x, viewportWidth) ? "open" : null;
24
- }
25
- function isInsideHorizontalScroller(target) {
26
- let node = target instanceof Element ? target : null;
27
- while (node && node !== document.body) {
28
- const { overflowX } = window.getComputedStyle(node);
29
- if ((overflowX === "auto" || overflowX === "scroll") && node.scrollWidth > node.clientWidth) {
30
- return true;
31
- }
32
- node = node.parentElement;
33
- }
34
- return false;
35
- }
36
-
37
- // src/drawer-swipe/use-drawer-swipe.ts
38
- import { useEffect, useRef } from "react";
39
- function isWideViewport(maxViewportWidth) {
40
- return window.matchMedia(`(min-width: ${maxViewportWidth}px)`).matches;
41
- }
42
- function useDrawerSwipe({
43
- drawerOpen,
44
- onOpen,
45
- onClose,
46
- enabled = true,
47
- maxViewportWidth = 768
48
- }) {
49
- const start = useRef(null);
50
- const handlers = useRef({ drawerOpen, onOpen, onClose, maxViewportWidth });
51
- handlers.current = { drawerOpen, onOpen, onClose, maxViewportWidth };
52
- useEffect(() => {
53
- if (!enabled) return;
54
- function handleStart(event) {
55
- start.current = null;
56
- if (event.touches.length !== 1) return;
57
- if (isWideViewport(handlers.current.maxViewportWidth)) return;
58
- if (!handlers.current.drawerOpen && isInsideHorizontalScroller(event.target)) {
59
- return;
60
- }
61
- const touch = event.touches[0];
62
- if (!touch) return;
63
- start.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
64
- }
65
- function handleMove(event) {
66
- const origin = start.current;
67
- const touch = event.touches[0];
68
- if (!origin || !touch) return;
69
- if (Math.abs(touch.clientY - origin.y) > VERTICAL_CANCEL_PX) {
70
- start.current = null;
71
- }
72
- }
73
- function handleEnd(event) {
74
- const origin = start.current;
75
- start.current = null;
76
- const touch = event.changedTouches[0];
77
- if (!origin || !touch) return;
78
- const { drawerOpen: drawerOpen2, onOpen: onOpen2, onClose: onClose2 } = handlers.current;
79
- const intent = resolveSwipeIntent(
80
- origin,
81
- { x: touch.clientX, y: touch.clientY, time: Date.now() },
82
- drawerOpen2,
83
- window.innerWidth
84
- );
85
- if (intent === "open") onOpen2();
86
- if (intent === "close") onClose2();
87
- }
88
- function handleCancel() {
89
- start.current = null;
90
- }
91
- document.addEventListener("touchstart", handleStart, { passive: true });
92
- document.addEventListener("touchmove", handleMove, { passive: true });
93
- document.addEventListener("touchend", handleEnd, { passive: true });
94
- document.addEventListener("touchcancel", handleCancel, { passive: true });
95
- return () => {
96
- document.removeEventListener("touchstart", handleStart);
97
- document.removeEventListener("touchmove", handleMove);
98
- document.removeEventListener("touchend", handleEnd);
99
- document.removeEventListener("touchcancel", handleCancel);
100
- };
101
- }, [enabled]);
102
- }
2
+ import {
3
+ BACK_ZONE_PX,
4
+ CLOSE_DISTANCE_PX,
5
+ FORWARD_ZONE_PX,
6
+ MAX_SWIPE_DURATION_MS,
7
+ OPEN_DISTANCE_PX,
8
+ VERTICAL_CANCEL_PX,
9
+ isInOpenZone,
10
+ isInsideHorizontalScroller,
11
+ resolveSwipeIntent,
12
+ useDrawerSwipe
13
+ } from "./chunk-OZ4PU4E3.js";
103
14
  export {
104
15
  BACK_ZONE_PX,
105
16
  CLOSE_DISTANCE_PX,
package/dist/escape.cjs CHANGED
@@ -35,6 +35,7 @@ function useEscape(onEscape, active = true) {
35
35
  (0, import_react.useEffect)(() => {
36
36
  if (!active) return;
37
37
  function onKeyDown(event) {
38
+ if (event.isComposing || event.keyCode === 229) return;
38
39
  if (event.key === "Escape") handlerRef.current();
39
40
  }
40
41
  document.addEventListener("keydown", onKeyDown);
package/dist/escape.js CHANGED
@@ -1,21 +1,7 @@
1
1
  "use client";
2
-
3
- // src/escape/use-escape.ts
4
- import { useEffect, useRef } from "react";
5
- function useEscape(onEscape, active = true) {
6
- const handlerRef = useRef(onEscape);
7
- useEffect(() => {
8
- handlerRef.current = onEscape;
9
- });
10
- useEffect(() => {
11
- if (!active) return;
12
- function onKeyDown(event) {
13
- if (event.key === "Escape") handlerRef.current();
14
- }
15
- document.addEventListener("keydown", onKeyDown);
16
- return () => document.removeEventListener("keydown", onKeyDown);
17
- }, [active]);
18
- }
2
+ import {
3
+ useEscape
4
+ } from "./chunk-S4YKMGQE.js";
19
5
  export {
20
6
  useEscape
21
7
  };
@@ -58,6 +58,9 @@ function useFocusTrap(containerRef, {
58
58
  if (active && !openerRef.current && typeof document !== "undefined") {
59
59
  openerRef.current = document.activeElement;
60
60
  }
61
+ (0, import_react.useEffect)(() => {
62
+ if (!active) openerRef.current = null;
63
+ }, [active]);
61
64
  (0, import_react.useEffect)(() => {
62
65
  const container = containerRef.current;
63
66
  if (!active || !container) return;
@@ -94,7 +97,6 @@ function useFocusTrap(containerRef, {
94
97
  document.removeEventListener("keydown", onKeyDown);
95
98
  activeTraps.splice(activeTraps.indexOf(token), 1);
96
99
  const opener = returnFocusRef?.current ?? openerRef.current;
97
- openerRef.current = null;
98
100
  if (restoreFocus && opener?.isConnected) opener.focus();
99
101
  };
100
102
  }, [active, containerRef, initialFocusRef, returnFocusRef, restoreFocus]);
@@ -1,76 +1,10 @@
1
1
  "use client";
2
-
3
- // src/focus-trap/focusables.ts
4
- var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
5
- function focusableWithin(container) {
6
- return Array.from(
7
- container.querySelectorAll(FOCUSABLE_SELECTOR)
8
- ).filter((el) => !el.closest("[hidden], [inert]"));
9
- }
10
- function nextFocusTarget(items, current, shiftKey, container) {
11
- const first = items[0];
12
- const last = items[items.length - 1];
13
- if (!first || !last) return null;
14
- if (!current || !container.contains(current)) return shiftKey ? last : first;
15
- if (shiftKey && (current === first || current === container)) return last;
16
- if (!shiftKey && current === last) return first;
17
- return null;
18
- }
19
-
20
- // src/focus-trap/use-focus-trap.ts
21
- import { useEffect, useRef } from "react";
22
- var activeTraps = [];
23
- function useFocusTrap(containerRef, {
24
- active,
25
- initialFocusRef,
26
- returnFocusRef,
27
- restoreFocus = true
28
- }) {
29
- const openerRef = useRef(null);
30
- if (active && !openerRef.current && typeof document !== "undefined") {
31
- openerRef.current = document.activeElement;
32
- }
33
- useEffect(() => {
34
- const container = containerRef.current;
35
- if (!active || !container) return;
36
- const token = {};
37
- activeTraps.push(token);
38
- if (!container.contains(document.activeElement)) {
39
- const target = initialFocusRef?.current ?? focusableWithin(container)[0] ?? container;
40
- if (target === container && !container.hasAttribute("tabindex")) {
41
- container.setAttribute("tabindex", "-1");
42
- }
43
- target.focus();
44
- }
45
- function onKeyDown(event) {
46
- if (event.key !== "Tab" || !container) return;
47
- if (activeTraps[activeTraps.length - 1] !== token) return;
48
- const items = focusableWithin(container);
49
- if (items.length === 0) {
50
- event.preventDefault();
51
- return;
52
- }
53
- const target = nextFocusTarget(
54
- items,
55
- document.activeElement,
56
- event.shiftKey,
57
- container
58
- );
59
- if (target) {
60
- event.preventDefault();
61
- target.focus();
62
- }
63
- }
64
- document.addEventListener("keydown", onKeyDown);
65
- return () => {
66
- document.removeEventListener("keydown", onKeyDown);
67
- activeTraps.splice(activeTraps.indexOf(token), 1);
68
- const opener = returnFocusRef?.current ?? openerRef.current;
69
- openerRef.current = null;
70
- if (restoreFocus && opener?.isConnected) opener.focus();
71
- };
72
- }, [active, containerRef, initialFocusRef, returnFocusRef, restoreFocus]);
73
- }
2
+ import {
3
+ FOCUSABLE_SELECTOR,
4
+ focusableWithin,
5
+ nextFocusTarget,
6
+ useFocusTrap
7
+ } from "./chunk-DNU57XEJ.js";
74
8
  export {
75
9
  FOCUSABLE_SELECTOR,
76
10
  focusableWithin,
@@ -0,0 +1,127 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/hotkey/index.ts
22
+ var hotkey_exports = {};
23
+ __export(hotkey_exports, {
24
+ detectApple: () => detectApple,
25
+ isEditableTarget: () => isEditableTarget,
26
+ matchHotkey: () => matchHotkey,
27
+ parseHotkey: () => parseHotkey,
28
+ useHotkey: () => useHotkey
29
+ });
30
+ module.exports = __toCommonJS(hotkey_exports);
31
+
32
+ // src/hotkey/parse-hotkey.ts
33
+ var KEY_ALIASES = {
34
+ esc: "escape",
35
+ space: " ",
36
+ plus: "+",
37
+ up: "arrowup",
38
+ down: "arrowdown",
39
+ left: "arrowleft",
40
+ right: "arrowright"
41
+ };
42
+ function parseHotkey(combo) {
43
+ const parsed = { key: "", mod: false, ctrl: false, meta: false, alt: false, shift: false };
44
+ const parts = combo.trim().toLowerCase().split(/\+(?=.)/);
45
+ for (const part of parts) {
46
+ const token = part.trim();
47
+ if (token === "mod" || token === "ctrl" || token === "meta" || token === "alt" || token === "shift") {
48
+ parsed[token] = true;
49
+ } else {
50
+ parsed.key = KEY_ALIASES[token] ?? token;
51
+ }
52
+ }
53
+ if (!parsed.key) throw new Error(`Invalid hotkey "${combo}": no key`);
54
+ return parsed;
55
+ }
56
+
57
+ // src/hotkey/match-hotkey.ts
58
+ var CODE_FOR_SYMBOL = {
59
+ "/": "Slash",
60
+ ".": "Period",
61
+ ",": "Comma",
62
+ ";": "Semicolon",
63
+ "'": "Quote",
64
+ "[": "BracketLeft",
65
+ "]": "BracketRight",
66
+ "\\": "Backslash",
67
+ "-": "Minus",
68
+ "=": "Equal",
69
+ "`": "Backquote"
70
+ };
71
+ function physicalCode(key) {
72
+ if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
73
+ if (/^[0-9]$/.test(key)) return `Digit${key}`;
74
+ return CODE_FOR_SYMBOL[key] ?? null;
75
+ }
76
+ function keyMatches(event, parsed) {
77
+ if (event.key.toLowerCase() === parsed.key) return true;
78
+ return parsed.shift && physicalCode(parsed.key) === event.code;
79
+ }
80
+ function matchHotkey(event, combo, isApple) {
81
+ const parsed = typeof combo === "string" ? parseHotkey(combo) : combo;
82
+ const wantMeta = parsed.meta || parsed.mod && isApple;
83
+ const wantCtrl = parsed.ctrl || parsed.mod && !isApple;
84
+ return event.metaKey === wantMeta && event.ctrlKey === wantCtrl && event.altKey === parsed.alt && event.shiftKey === parsed.shift && keyMatches(event, parsed);
85
+ }
86
+ function detectApple(nav) {
87
+ if (!nav) return false;
88
+ const platform = nav.userAgentData?.platform || nav.platform || "";
89
+ return /mac|iphone|ipad|ipod/i.test(platform);
90
+ }
91
+ function isEditableTarget(target) {
92
+ const el = target;
93
+ if (!el || typeof el.tagName !== "string") return false;
94
+ const tag = el.tagName.toLowerCase();
95
+ return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable === true;
96
+ }
97
+
98
+ // src/hotkey/use-hotkey.ts
99
+ var import_react = require("react");
100
+ function useHotkey(combo, onHotkey, options = {}) {
101
+ const { enabled = true, allowInInputs = false, preventDefault = true } = options;
102
+ const handlerRef = (0, import_react.useRef)(onHotkey);
103
+ (0, import_react.useEffect)(() => {
104
+ handlerRef.current = onHotkey;
105
+ });
106
+ (0, import_react.useEffect)(() => {
107
+ if (!enabled) return;
108
+ const parsed = parseHotkey(combo);
109
+ function onKeyDown(event) {
110
+ if (event.isComposing || event.keyCode === 229) return;
111
+ if (!allowInInputs && isEditableTarget(event.target)) return;
112
+ if (!matchHotkey(event, parsed, detectApple(navigator))) return;
113
+ if (preventDefault) event.preventDefault();
114
+ handlerRef.current(event);
115
+ }
116
+ document.addEventListener("keydown", onKeyDown);
117
+ return () => document.removeEventListener("keydown", onKeyDown);
118
+ }, [combo, enabled, allowInInputs, preventDefault]);
119
+ }
120
+ // Annotate the CommonJS export names for ESM import in node:
121
+ 0 && (module.exports = {
122
+ detectApple,
123
+ isEditableTarget,
124
+ matchHotkey,
125
+ parseHotkey,
126
+ useHotkey
127
+ });
@@ -0,0 +1,31 @@
1
+ interface ParsedHotkey {
2
+ key: string;
3
+ mod: boolean;
4
+ ctrl: boolean;
5
+ meta: boolean;
6
+ alt: boolean;
7
+ shift: boolean;
8
+ }
9
+ declare function parseHotkey(combo: string): ParsedHotkey;
10
+
11
+ type KeyEventLike = Pick<KeyboardEvent, 'key' | 'code' | 'ctrlKey' | 'metaKey' | 'altKey' | 'shiftKey'>;
12
+ /** `isApple` decides what `mod` means: Cmd on Apple devices, Ctrl elsewhere. */
13
+ declare function matchHotkey(event: KeyEventLike, combo: string | ParsedHotkey, isApple: boolean): boolean;
14
+ interface NavigatorLike {
15
+ platform?: string;
16
+ userAgentData?: {
17
+ platform?: string;
18
+ };
19
+ }
20
+ /** iPad in desktop mode reports MacIntel, so it counts as Apple. */
21
+ declare function detectApple(nav: NavigatorLike | undefined): boolean;
22
+ declare function isEditableTarget(target: EventTarget | null): boolean;
23
+
24
+ interface UseHotkeyOptions {
25
+ enabled?: boolean;
26
+ allowInInputs?: boolean;
27
+ preventDefault?: boolean;
28
+ }
29
+ declare function useHotkey(combo: string, onHotkey: (event: KeyboardEvent) => void, options?: UseHotkeyOptions): void;
30
+
31
+ export { type ParsedHotkey, type UseHotkeyOptions, detectApple, isEditableTarget, matchHotkey, parseHotkey, useHotkey };
@@ -0,0 +1,31 @@
1
+ interface ParsedHotkey {
2
+ key: string;
3
+ mod: boolean;
4
+ ctrl: boolean;
5
+ meta: boolean;
6
+ alt: boolean;
7
+ shift: boolean;
8
+ }
9
+ declare function parseHotkey(combo: string): ParsedHotkey;
10
+
11
+ type KeyEventLike = Pick<KeyboardEvent, 'key' | 'code' | 'ctrlKey' | 'metaKey' | 'altKey' | 'shiftKey'>;
12
+ /** `isApple` decides what `mod` means: Cmd on Apple devices, Ctrl elsewhere. */
13
+ declare function matchHotkey(event: KeyEventLike, combo: string | ParsedHotkey, isApple: boolean): boolean;
14
+ interface NavigatorLike {
15
+ platform?: string;
16
+ userAgentData?: {
17
+ platform?: string;
18
+ };
19
+ }
20
+ /** iPad in desktop mode reports MacIntel, so it counts as Apple. */
21
+ declare function detectApple(nav: NavigatorLike | undefined): boolean;
22
+ declare function isEditableTarget(target: EventTarget | null): boolean;
23
+
24
+ interface UseHotkeyOptions {
25
+ enabled?: boolean;
26
+ allowInInputs?: boolean;
27
+ preventDefault?: boolean;
28
+ }
29
+ declare function useHotkey(combo: string, onHotkey: (event: KeyboardEvent) => void, options?: UseHotkeyOptions): void;
30
+
31
+ export { type ParsedHotkey, type UseHotkeyOptions, detectApple, isEditableTarget, matchHotkey, parseHotkey, useHotkey };
package/dist/hotkey.js ADDED
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import {
3
+ detectApple,
4
+ isEditableTarget,
5
+ matchHotkey,
6
+ parseHotkey,
7
+ useHotkey
8
+ } from "./chunk-NKLEB5TR.js";
9
+ export {
10
+ detectApple,
11
+ isEditableTarget,
12
+ matchHotkey,
13
+ parseHotkey,
14
+ useHotkey
15
+ };