@jbpark/use-hooks 2.11.0 → 3.0.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 (59) hide show
  1. package/dist/hooks/index.d.mts +12 -1
  2. package/dist/hooks/index.mjs +12 -1
  3. package/dist/hooks/use-click-outside.d.mts +9 -2
  4. package/dist/hooks/use-click-outside.mjs +27 -11
  5. package/dist/hooks/use-click-outside.mjs.map +1 -1
  6. package/dist/hooks/use-debounced-value.d.mts +5 -0
  7. package/dist/hooks/use-debounced-value.mjs +20 -0
  8. package/dist/hooks/use-debounced-value.mjs.map +1 -0
  9. package/dist/hooks/use-event-listener.d.mts +21 -0
  10. package/dist/hooks/use-event-listener.mjs +37 -0
  11. package/dist/hooks/use-event-listener.mjs.map +1 -0
  12. package/dist/hooks/use-file-drop.d.mts +14 -0
  13. package/dist/hooks/use-file-drop.mjs +85 -0
  14. package/dist/hooks/use-file-drop.mjs.map +1 -0
  15. package/dist/hooks/use-image.d.mts +2 -1
  16. package/dist/hooks/use-image.mjs +4 -3
  17. package/dist/hooks/use-image.mjs.map +1 -1
  18. package/dist/hooks/use-intersection-observer.d.mts +8 -1
  19. package/dist/hooks/use-intersection-observer.mjs +37 -5
  20. package/dist/hooks/use-intersection-observer.mjs.map +1 -1
  21. package/dist/hooks/use-interval.d.mts +5 -0
  22. package/dist/hooks/use-interval.mjs +20 -0
  23. package/dist/hooks/use-interval.mjs.map +1 -0
  24. package/dist/hooks/use-key-press.d.mts +14 -0
  25. package/dist/hooks/use-key-press.mjs +63 -0
  26. package/dist/hooks/use-key-press.mjs.map +1 -0
  27. package/dist/hooks/use-merged-ref.d.mts +8 -0
  28. package/dist/hooks/use-merged-ref.mjs +25 -0
  29. package/dist/hooks/use-merged-ref.mjs.map +1 -0
  30. package/dist/hooks/use-mutation-observer.d.mts +11 -0
  31. package/dist/hooks/use-mutation-observer.mjs +33 -0
  32. package/dist/hooks/use-mutation-observer.mjs.map +1 -0
  33. package/dist/hooks/use-previous.d.mts +5 -0
  34. package/dist/hooks/use-previous.mjs +14 -0
  35. package/dist/hooks/use-previous.mjs.map +1 -0
  36. package/dist/hooks/use-resize-observer.d.mts +12 -0
  37. package/dist/hooks/use-resize-observer.mjs +29 -0
  38. package/dist/hooks/use-resize-observer.mjs.map +1 -0
  39. package/dist/hooks/use-scroll-to-elements.d.mts +7 -5
  40. package/dist/hooks/use-scroll-to-elements.mjs +52 -27
  41. package/dist/hooks/use-scroll-to-elements.mjs.map +1 -1
  42. package/dist/hooks/use-throttle.d.mts +5 -1
  43. package/dist/hooks/use-throttle.mjs +6 -32
  44. package/dist/hooks/use-throttle.mjs.map +1 -1
  45. package/dist/hooks/use-throttled-callback.d.mts +12 -0
  46. package/dist/hooks/use-throttled-callback.mjs +54 -0
  47. package/dist/hooks/use-throttled-callback.mjs.map +1 -0
  48. package/dist/hooks/use-timeout.d.mts +8 -0
  49. package/dist/hooks/use-timeout.mjs +38 -0
  50. package/dist/hooks/use-timeout.mjs.map +1 -0
  51. package/dist/hooks/use-toggle.d.mts +5 -0
  52. package/dist/hooks/use-toggle.mjs +17 -0
  53. package/dist/hooks/use-toggle.mjs.map +1 -0
  54. package/dist/index.d.mts +13 -2
  55. package/dist/index.mjs +13 -2
  56. package/package.json +1 -1
  57. package/dist/hooks/use-timeline.d.mts +0 -50
  58. package/dist/hooks/use-timeline.mjs +0 -211
  59. package/dist/hooks/use-timeline.mjs.map +0 -1
@@ -0,0 +1,63 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/use-key-press.ts
4
+ const isMac = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
5
+ const parseCombo = (combo) => {
6
+ const parts = combo.split("+").map((part) => part.trim().toLowerCase());
7
+ const key = parts[parts.length - 1] ?? "";
8
+ const modifiers = parts.slice(0, -1);
9
+ let ctrl = modifiers.includes("ctrl");
10
+ let meta = modifiers.includes("meta") || modifiers.includes("cmd");
11
+ const shift = modifiers.includes("shift");
12
+ const alt = modifiers.includes("alt") || modifiers.includes("option");
13
+ if (modifiers.includes("mod")) if (isMac()) meta = true;
14
+ else ctrl = true;
15
+ return {
16
+ key,
17
+ ctrl,
18
+ meta,
19
+ shift,
20
+ alt
21
+ };
22
+ };
23
+ const matchesCombo = (event, combo) => event.key.toLowerCase() === combo.key && event.ctrlKey === combo.ctrl && event.metaKey === combo.meta && event.shiftKey === combo.shift && event.altKey === combo.alt;
24
+ const resolveTarget = (target) => {
25
+ if (!target) return typeof window === "undefined" ? null : window;
26
+ return "current" in target ? target.current : target;
27
+ };
28
+ const useKeyPress = (combo, handler, options = {}) => {
29
+ const { target, enabled = true, preventDefault = false, ignore } = options;
30
+ const handlerRef = useRef(handler);
31
+ useEffect(() => {
32
+ handlerRef.current = handler;
33
+ });
34
+ const combos = Array.isArray(combo) ? combo : [combo];
35
+ const parsedCombos = combos.map(parseCombo);
36
+ useEffect(() => {
37
+ if (!enabled) return;
38
+ const resolvedTarget = resolveTarget(target);
39
+ if (!resolvedTarget) return;
40
+ const onKeyDown = (event) => {
41
+ const keyboardEvent = event;
42
+ const eventTarget = keyboardEvent.target;
43
+ if (ignore && eventTarget?.closest(ignore)) return;
44
+ if (!parsedCombos.some((parsed) => matchesCombo(keyboardEvent, parsed))) return;
45
+ if (preventDefault) keyboardEvent.preventDefault();
46
+ handlerRef.current(keyboardEvent);
47
+ };
48
+ resolvedTarget.addEventListener("keydown", onKeyDown);
49
+ return () => {
50
+ resolvedTarget.removeEventListener("keydown", onKeyDown);
51
+ };
52
+ }, [
53
+ enabled,
54
+ combos.join(","),
55
+ target,
56
+ preventDefault,
57
+ ignore
58
+ ]);
59
+ };
60
+
61
+ //#endregion
62
+ export { useKeyPress as default };
63
+ //# sourceMappingURL=use-key-press.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-key-press.mjs","names":[],"sources":["../../src/hooks/use-key-press.ts"],"sourcesContent":["import { type RefObject, useEffect, useRef } from 'react';\n\ntype KeyPressTarget =\n | RefObject<HTMLElement | null>\n | Window\n | Document\n | HTMLElement\n | null\n | undefined;\n\ninterface Options {\n target?: KeyPressTarget;\n enabled?: boolean;\n preventDefault?: boolean;\n // CSS selector — a keydown whose target is inside a matching element is\n // ignored (e.g. don't hijack Cmd+Z while the user is typing in a code\n // editor that has its own undo).\n ignore?: string;\n}\n\ninterface ParsedCombo {\n key: string;\n ctrl: boolean;\n meta: boolean;\n shift: boolean;\n alt: boolean;\n}\n\nconst isMac = () =>\n typeof navigator !== 'undefined' &&\n /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);\n\n// `mod` normalizes to the platform's usual \"primary\" modifier (Cmd on\n// macOS, Ctrl elsewhere) so a single combo string covers both without the\n// caller branching on platform themselves.\nconst parseCombo = (combo: string): ParsedCombo => {\n const parts = combo.split('+').map(part => part.trim().toLowerCase());\n const key = parts[parts.length - 1] ?? '';\n const modifiers = parts.slice(0, -1);\n\n let ctrl = modifiers.includes('ctrl');\n let meta = modifiers.includes('meta') || modifiers.includes('cmd');\n const shift = modifiers.includes('shift');\n const alt = modifiers.includes('alt') || modifiers.includes('option');\n\n if (modifiers.includes('mod')) {\n if (isMac()) {\n meta = true;\n } else {\n ctrl = true;\n }\n }\n\n return { key, ctrl, meta, shift, alt };\n};\n\n// Modifiers not named in the combo are required to be *absent*, not just\n// ignored — otherwise 'mod+z' and 'mod+shift+z' registered as separate\n// bindings (the exact motivating case for this hook) would both fire on\n// the same Cmd+Shift+Z keypress instead of only the latter.\nconst matchesCombo = (event: KeyboardEvent, combo: ParsedCombo) =>\n event.key.toLowerCase() === combo.key &&\n event.ctrlKey === combo.ctrl &&\n event.metaKey === combo.meta &&\n event.shiftKey === combo.shift &&\n event.altKey === combo.alt;\n\nconst resolveTarget = (target: KeyPressTarget): EventTarget | null => {\n if (!target) {\n return typeof window === 'undefined' ? null : window;\n }\n return 'current' in target ? target.current : target;\n};\n\nconst useKeyPress = (\n combo: string | string[],\n handler: (event: KeyboardEvent) => void,\n options: Options = {},\n) => {\n const { target, enabled = true, preventDefault = false, ignore } = options;\n\n const handlerRef = useRef(handler);\n\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const combos = Array.isArray(combo) ? combo : [combo];\n const parsedCombos = combos.map(parseCombo);\n // `combos` is typically a fresh array literal at the call site — depend\n // on a stable string proxy instead so this doesn't tear down and\n // re-register the listener on every render. `target` itself (a\n // RefObject, or window/document/an element variable) is normally\n // already stable across renders, so it's used directly below.\n const comboKey = combos.join(',');\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const resolvedTarget = resolveTarget(target);\n\n if (!resolvedTarget) {\n return;\n }\n\n const onKeyDown = (event: Event) => {\n const keyboardEvent = event as KeyboardEvent;\n const eventTarget = keyboardEvent.target as HTMLElement | null;\n\n if (ignore && eventTarget?.closest(ignore)) {\n return;\n }\n\n if (!parsedCombos.some(parsed => matchesCombo(keyboardEvent, parsed))) {\n return;\n }\n\n if (preventDefault) {\n keyboardEvent.preventDefault();\n }\n\n handlerRef.current(keyboardEvent);\n };\n\n resolvedTarget.addEventListener('keydown', onKeyDown);\n\n return () => {\n resolvedTarget.removeEventListener('keydown', onKeyDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, comboKey, target, preventDefault, ignore]);\n};\n\nexport default useKeyPress;\n"],"mappings":";;;AA4BA,MAAM,cACJ,OAAO,cAAc,eACrB,uBAAuB,KAAK,UAAU,UAAU;AAKlD,MAAM,cAAc,UAA+B;CACjD,MAAM,QAAQ,MAAM,MAAM,IAAI,CAAC,KAAI,SAAQ,KAAK,MAAM,CAAC,aAAa,CAAC;CACrE,MAAM,MAAM,MAAM,MAAM,SAAS,MAAM;CACvC,MAAM,YAAY,MAAM,MAAM,GAAG,GAAG;CAEpC,IAAI,OAAO,UAAU,SAAS,OAAO;CACrC,IAAI,OAAO,UAAU,SAAS,OAAO,IAAI,UAAU,SAAS,MAAM;CAClE,MAAM,QAAQ,UAAU,SAAS,QAAQ;CACzC,MAAM,MAAM,UAAU,SAAS,MAAM,IAAI,UAAU,SAAS,SAAS;AAErE,KAAI,UAAU,SAAS,MAAM,CAC3B,KAAI,OAAO,CACT,QAAO;KAEP,QAAO;AAIX,QAAO;EAAE;EAAK;EAAM;EAAM;EAAO;EAAK;;AAOxC,MAAM,gBAAgB,OAAsB,UAC1C,MAAM,IAAI,aAAa,KAAK,MAAM,OAClC,MAAM,YAAY,MAAM,QACxB,MAAM,YAAY,MAAM,QACxB,MAAM,aAAa,MAAM,SACzB,MAAM,WAAW,MAAM;AAEzB,MAAM,iBAAiB,WAA+C;AACpE,KAAI,CAAC,OACH,QAAO,OAAO,WAAW,cAAc,OAAO;AAEhD,QAAO,aAAa,SAAS,OAAO,UAAU;;AAGhD,MAAM,eACJ,OACA,SACA,UAAmB,EAAE,KAClB;CACH,MAAM,EAAE,QAAQ,UAAU,MAAM,iBAAiB,OAAO,WAAW;CAEnE,MAAM,aAAa,OAAO,QAAQ;AAElC,iBAAgB;AACd,aAAW,UAAU;GACrB;CAEF,MAAM,SAAS,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;CACrD,MAAM,eAAe,OAAO,IAAI,WAAW;AAQ3C,iBAAgB;AACd,MAAI,CAAC,QACH;EAGF,MAAM,iBAAiB,cAAc,OAAO;AAE5C,MAAI,CAAC,eACH;EAGF,MAAM,aAAa,UAAiB;GAClC,MAAM,gBAAgB;GACtB,MAAM,cAAc,cAAc;AAElC,OAAI,UAAU,aAAa,QAAQ,OAAO,CACxC;AAGF,OAAI,CAAC,aAAa,MAAK,WAAU,aAAa,eAAe,OAAO,CAAC,CACnE;AAGF,OAAI,eACF,eAAc,gBAAgB;AAGhC,cAAW,QAAQ,cAAc;;AAGnC,iBAAe,iBAAiB,WAAW,UAAU;AAErD,eAAa;AACX,kBAAe,oBAAoB,WAAW,UAAU;;IAGzD;EAAC;EAtCa,OAAO,KAAK,IAAI;EAsCV;EAAQ;EAAgB;EAAO,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { RefObject } from "react";
2
+
3
+ //#region src/hooks/use-merged-ref.d.ts
4
+ type MergeableRef<T> = ((node: T | null) => void | (() => void)) | RefObject<T | null> | null | undefined;
5
+ declare const useMergedRef: <T>(...refs: MergeableRef<T>[]) => (node: T | null) => (() => void) | undefined;
6
+ //#endregion
7
+ export { useMergedRef };
8
+ //# sourceMappingURL=use-merged-ref.d.mts.map
@@ -0,0 +1,25 @@
1
+ import { useCallback } from "react";
2
+
3
+ //#region src/hooks/use-merged-ref.ts
4
+ const useMergedRef = (...refs) => {
5
+ return useCallback((node) => {
6
+ const cleanups = [];
7
+ refs.forEach((ref) => {
8
+ if (!ref) return;
9
+ if (typeof ref === "function") {
10
+ const cleanup = ref(node);
11
+ if (typeof cleanup === "function") cleanups.push(cleanup);
12
+ return;
13
+ }
14
+ ref.current = node;
15
+ });
16
+ if (cleanups.length === 0) return;
17
+ return () => {
18
+ cleanups.forEach((cleanup) => cleanup());
19
+ };
20
+ }, refs);
21
+ };
22
+
23
+ //#endregion
24
+ export { useMergedRef as default };
25
+ //# sourceMappingURL=use-merged-ref.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-merged-ref.mjs","names":[],"sources":["../../src/hooks/use-merged-ref.ts"],"sourcesContent":["import { type RefObject, useCallback } from 'react';\n\ntype MergeableRef<T> =\n | ((node: T | null) => void | (() => void))\n | RefObject<T | null>\n | null\n | undefined;\n\n// Merges any number of refs (forwarded function refs, RefObjects, or\n// either left null/undefined) into one callback ref that updates all of\n// them. Collects React 19's optional per-ref cleanup return values and\n// runs them together when the node detaches.\nconst useMergedRef = <T>(...refs: MergeableRef<T>[]) => {\n return useCallback((node: T | null) => {\n const cleanups: (() => void)[] = [];\n\n refs.forEach(ref => {\n if (!ref) {\n return;\n }\n\n if (typeof ref === 'function') {\n const cleanup = ref(node);\n\n if (typeof cleanup === 'function') {\n cleanups.push(cleanup);\n }\n return;\n }\n\n ref.current = node;\n });\n\n if (cleanups.length === 0) {\n return;\n }\n\n return () => {\n cleanups.forEach(cleanup => cleanup());\n };\n // The number of refs passed at a given call site is stable across\n // renders even though this array literal isn't — same pattern every\n // ref-merging hook of this shape relies on.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, refs);\n};\n\nexport default useMergedRef;\n"],"mappings":";;;AAYA,MAAM,gBAAmB,GAAG,SAA4B;AACtD,QAAO,aAAa,SAAmB;EACrC,MAAM,WAA2B,EAAE;AAEnC,OAAK,SAAQ,QAAO;AAClB,OAAI,CAAC,IACH;AAGF,OAAI,OAAO,QAAQ,YAAY;IAC7B,MAAM,UAAU,IAAI,KAAK;AAEzB,QAAI,OAAO,YAAY,WACrB,UAAS,KAAK,QAAQ;AAExB;;AAGF,OAAI,UAAU;IACd;AAEF,MAAI,SAAS,WAAW,EACtB;AAGF,eAAa;AACX,YAAS,SAAQ,YAAW,SAAS,CAAC;;IAMvC,KAAK"}
@@ -0,0 +1,11 @@
1
+ import { RefObject } from "react";
2
+
3
+ //#region src/hooks/use-mutation-observer.d.ts
4
+ type Target<T extends Node> = RefObject<T | null> | T | null | undefined;
5
+ interface Options extends MutationObserverInit {
6
+ enabled?: boolean;
7
+ }
8
+ declare const useMutationObserver: <T extends Node>(target: Target<T>, callback: MutationCallback, options?: Options) => void;
9
+ //#endregion
10
+ export { useMutationObserver };
11
+ //# sourceMappingURL=use-mutation-observer.d.mts.map
@@ -0,0 +1,33 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/use-mutation-observer.ts
4
+ const resolveTarget = (target) => {
5
+ if (!target) return null;
6
+ return "current" in target ? target.current : target;
7
+ };
8
+ const useMutationObserver = (target, callback, options = {}) => {
9
+ const { enabled = true, ...mutationOptions } = options;
10
+ const callbackRef = useRef(callback);
11
+ useEffect(() => {
12
+ callbackRef.current = callback;
13
+ });
14
+ useEffect(() => {
15
+ const node = resolveTarget(target);
16
+ if (!enabled || !node || typeof MutationObserver === "undefined") return;
17
+ const observer = new MutationObserver((mutations, obs) => {
18
+ callbackRef.current(mutations, obs);
19
+ });
20
+ observer.observe(node, mutationOptions);
21
+ return () => {
22
+ observer.disconnect();
23
+ };
24
+ }, [
25
+ target,
26
+ enabled,
27
+ JSON.stringify(mutationOptions)
28
+ ]);
29
+ };
30
+
31
+ //#endregion
32
+ export { useMutationObserver as default };
33
+ //# sourceMappingURL=use-mutation-observer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mutation-observer.mjs","names":[],"sources":["../../src/hooks/use-mutation-observer.ts"],"sourcesContent":["import { type RefObject, useEffect, useRef } from 'react';\n\ntype Target<T extends Node> = RefObject<T | null> | T | null | undefined;\n\ninterface Options extends MutationObserverInit {\n enabled?: boolean;\n}\n\nconst resolveTarget = <T extends Node>(target: Target<T>): T | null => {\n if (!target) {\n return null;\n }\n return 'current' in target ? target.current : target;\n};\n\n// Takes the target directly (a RefObject, or a plain Node like\n// document.head that isn't behind any React ref at all) rather than\n// producing its own — this and useResizeObserver together replace the\n// ResizeObserver+MutationObserver pair live-editor's iframe hand-rolls\n// for auto-sizing its preview content.\nconst useMutationObserver = <T extends Node>(\n target: Target<T>,\n callback: MutationCallback,\n options: Options = {},\n) => {\n const { enabled = true, ...mutationOptions } = options;\n\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n // `mutationOptions` is typically a fresh object literal at the call\n // site — serialize it into a stable key instead of depending on the\n // object itself, which would tear down and recreate the observer on\n // every render.\n const optionsKey = JSON.stringify(mutationOptions);\n\n useEffect(() => {\n const node = resolveTarget(target);\n\n if (!enabled || !node || typeof MutationObserver === 'undefined') {\n return;\n }\n\n const observer = new MutationObserver((mutations, obs) => {\n callbackRef.current(mutations, obs);\n });\n\n observer.observe(node, mutationOptions);\n\n return () => {\n observer.disconnect();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [target, enabled, optionsKey]);\n};\n\nexport default useMutationObserver;\n"],"mappings":";;;AAQA,MAAM,iBAAiC,WAAgC;AACrE,KAAI,CAAC,OACH,QAAO;AAET,QAAO,aAAa,SAAS,OAAO,UAAU;;AAQhD,MAAM,uBACJ,QACA,UACA,UAAmB,EAAE,KAClB;CACH,MAAM,EAAE,UAAU,MAAM,GAAG,oBAAoB;CAE/C,MAAM,cAAc,OAAO,SAAS;AAEpC,iBAAgB;AACd,cAAY,UAAU;GACtB;AAQF,iBAAgB;EACd,MAAM,OAAO,cAAc,OAAO;AAElC,MAAI,CAAC,WAAW,CAAC,QAAQ,OAAO,qBAAqB,YACnD;EAGF,MAAM,WAAW,IAAI,kBAAkB,WAAW,QAAQ;AACxD,eAAY,QAAQ,WAAW,IAAI;IACnC;AAEF,WAAS,QAAQ,MAAM,gBAAgB;AAEvC,eAAa;AACX,YAAS,YAAY;;IAGtB;EAAC;EAAQ;EAnBO,KAAK,UAAU,gBAAgB;EAmBlB,CAAC"}
@@ -0,0 +1,5 @@
1
+ //#region src/hooks/use-previous.d.ts
2
+ declare const usePrevious: <T>(value: T) => T | undefined;
3
+ //#endregion
4
+ export { usePrevious };
5
+ //# sourceMappingURL=use-previous.d.mts.map
@@ -0,0 +1,14 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/use-previous.ts
4
+ const usePrevious = (value) => {
5
+ const ref = useRef(void 0);
6
+ useEffect(() => {
7
+ ref.current = value;
8
+ });
9
+ return ref.current;
10
+ };
11
+
12
+ //#endregion
13
+ export { usePrevious as default };
14
+ //# sourceMappingURL=use-previous.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-previous.mjs","names":[],"sources":["../../src/hooks/use-previous.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\n// Returns the value from the *previous* render. Updated in an effect\n// (which runs after render/paint) rather than during render, so the\n// current render still reads whatever was current one render ago.\nconst usePrevious = <T>(value: T): T | undefined => {\n const ref = useRef<T | undefined>(undefined);\n\n useEffect(() => {\n ref.current = value;\n });\n\n return ref.current;\n};\n\nexport default usePrevious;\n"],"mappings":";;;AAKA,MAAM,eAAkB,UAA4B;CAClD,MAAM,MAAM,OAAsB,OAAU;AAE5C,iBAAgB;AACd,MAAI,UAAU;GACd;AAEF,QAAO,IAAI"}
@@ -0,0 +1,12 @@
1
+ //#region src/hooks/use-resize-observer.d.ts
2
+ interface Size {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ interface Options {
7
+ box?: ResizeObserverBoxOptions;
8
+ }
9
+ declare const useResizeObserver: <T extends Element = Element>(options?: Options) => [(node: T | null) => void, Size | null];
10
+ //#endregion
11
+ export { useResizeObserver };
12
+ //# sourceMappingURL=use-resize-observer.d.mts.map
@@ -0,0 +1,29 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+
3
+ //#region src/hooks/use-resize-observer.ts
4
+ const useResizeObserver = (options) => {
5
+ const [size, setSize] = useState(null);
6
+ const observerRef = useRef(null);
7
+ return [useCallback((node) => {
8
+ if (observerRef.current) {
9
+ observerRef.current.disconnect();
10
+ observerRef.current = null;
11
+ }
12
+ if (!node || typeof ResizeObserver === "undefined") return;
13
+ const box = options?.box ?? "content-box";
14
+ const observer = new ResizeObserver(([entry]) => {
15
+ if (!entry) return;
16
+ const [{ inlineSize, blockSize }] = box === "border-box" ? entry.borderBoxSize : entry.contentBoxSize;
17
+ setSize({
18
+ width: inlineSize,
19
+ height: blockSize
20
+ });
21
+ });
22
+ observer.observe(node, { box });
23
+ observerRef.current = observer;
24
+ }, []), size];
25
+ };
26
+
27
+ //#endregion
28
+ export { useResizeObserver as default };
29
+ //# sourceMappingURL=use-resize-observer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-resize-observer.mjs","names":[],"sources":["../../src/hooks/use-resize-observer.ts"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\n\ninterface Size {\n width: number;\n height: number;\n}\n\ninterface Options {\n box?: ResizeObserverBoxOptions;\n}\n\n// The unprocessed version of useResponsiveSize/useElementScroll/\n// useElementPosition — those each return values shaped for their own\n// purpose (breakpoints, scroll position, a viewport-relative DOMRect).\n// This one just reports an element's own width/height.\nconst useResizeObserver = <T extends Element = Element>(\n options?: Options,\n): [(node: T | null) => void, Size | null] => {\n const [size, setSize] = useState<Size | null>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n // Same convention as useIntersectionObserver: `options` is typically a\n // fresh object literal at the call site, so it isn't a dependency here\n // — that would tear down and recreate the observer on every render. The\n // ref callback only re-runs when the observed node itself changes,\n // capturing whatever `options` was current at that time.\n const ref = useCallback((node: T | null) => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n observerRef.current = null;\n }\n\n if (!node || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const box = options?.box ?? 'content-box';\n\n const observer = new ResizeObserver(([entry]) => {\n if (!entry) {\n return;\n }\n\n const [{ inlineSize, blockSize }] =\n box === 'border-box' ? entry.borderBoxSize : entry.contentBoxSize;\n\n setSize({ width: inlineSize, height: blockSize });\n });\n\n observer.observe(node, { box });\n observerRef.current = observer;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [ref, size];\n};\n\nexport default useResizeObserver;\n"],"mappings":";;;AAeA,MAAM,qBACJ,YAC4C;CAC5C,MAAM,CAAC,MAAM,WAAW,SAAsB,KAAK;CACnD,MAAM,cAAc,OAA8B,KAAK;AAmCvD,QAAO,CA5BK,aAAa,SAAmB;AAC1C,MAAI,YAAY,SAAS;AACvB,eAAY,QAAQ,YAAY;AAChC,eAAY,UAAU;;AAGxB,MAAI,CAAC,QAAQ,OAAO,mBAAmB,YACrC;EAGF,MAAM,MAAM,SAAS,OAAO;EAE5B,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;AAC/C,OAAI,CAAC,MACH;GAGF,MAAM,CAAC,EAAE,YAAY,eACnB,QAAQ,eAAe,MAAM,gBAAgB,MAAM;AAErD,WAAQ;IAAE,OAAO;IAAY,QAAQ;IAAW,CAAC;IACjD;AAEF,WAAS,QAAQ,MAAM,EAAE,KAAK,CAAC;AAC/B,cAAY,UAAU;IAErB,EAAE,CAAC,EAEO,KAAK"}
@@ -1,13 +1,15 @@
1
- import * as react from "react";
1
+ import { RefObject } from "react";
2
2
 
3
3
  //#region src/hooks/use-scroll-to-elements.d.ts
4
+ type ElementKey = string;
5
+ type ScrollContainer = HTMLElement | RefObject<HTMLElement | null> | null;
4
6
  interface Options extends ScrollIntoViewOptions {
5
7
  offset?: number;
8
+ container?: ScrollContainer;
6
9
  }
7
- declare const useScrollToElements: (options?: Options) => {
8
- elementRefs: react.RefObject<(HTMLElement | null)[]>;
9
- setElementRef: (element: HTMLElement | null, index: number) => void;
10
- scrollToElement: (index: number) => void;
10
+ declare const useScrollToElements: (defaultOptions?: Options) => {
11
+ register: (key: ElementKey) => (node: HTMLElement | null) => void;
12
+ scrollTo: (key: ElementKey, options?: Options) => void;
11
13
  };
12
14
  //#endregion
13
15
  export { useScrollToElements };
@@ -1,37 +1,62 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
2
 
3
3
  //#region src/hooks/use-scroll-to-elements.ts
4
- const useScrollToElements = (options) => {
5
- const elementRefs = useRef([]);
6
- const optionsRef = useRef(options);
4
+ const resolveContainer = (container) => {
5
+ if (!container) return null;
6
+ return "current" in container ? container.current : container;
7
+ };
8
+ const useScrollToElements = (defaultOptions) => {
9
+ const elementsRef = useRef(/* @__PURE__ */ new Map());
10
+ const registerCallbacksRef = useRef(/* @__PURE__ */ new Map());
11
+ const defaultOptionsRef = useRef(defaultOptions);
7
12
  useEffect(() => {
8
- optionsRef.current = options;
13
+ defaultOptionsRef.current = defaultOptions;
9
14
  });
10
- const scrollToElement = useCallback((index) => {
11
- const element = elementRefs.current[index];
12
- if (!element) return;
13
- const { offset, ...scrollIntoViewOptions } = optionsRef.current ?? {};
14
- if (offset != null) {
15
- const top = element.getBoundingClientRect().top + window.scrollY - offset;
16
- window.scrollTo({
17
- top,
18
- behavior: scrollIntoViewOptions.behavior || "smooth"
19
- });
20
- return;
21
- }
22
- element.scrollIntoView({
23
- behavior: "smooth",
24
- block: "start",
25
- inline: "start",
26
- ...scrollIntoViewOptions
27
- });
28
- }, []);
29
15
  return {
30
- elementRefs,
31
- setElementRef: useCallback((element, index) => {
32
- elementRefs.current[index] = element;
16
+ register: useCallback((key) => {
17
+ let callback = registerCallbacksRef.current.get(key);
18
+ if (!callback) {
19
+ callback = (node) => {
20
+ if (node) elementsRef.current.set(key, node);
21
+ else elementsRef.current.delete(key);
22
+ };
23
+ registerCallbacksRef.current.set(key, callback);
24
+ }
25
+ return callback;
33
26
  }, []),
34
- scrollToElement
27
+ scrollTo: useCallback((key, options) => {
28
+ const element = elementsRef.current.get(key);
29
+ if (!element) return;
30
+ const { offset, container, ...scrollIntoViewOptions } = {
31
+ ...defaultOptionsRef.current,
32
+ ...options
33
+ };
34
+ if (offset != null) {
35
+ const containerElement = resolveContainer(container ?? null);
36
+ if (containerElement) {
37
+ const containerRect = containerElement.getBoundingClientRect();
38
+ const elementRect = element.getBoundingClientRect();
39
+ const top = containerElement.scrollTop + (elementRect.top - containerRect.top) - offset;
40
+ containerElement.scrollTo({
41
+ top,
42
+ behavior: scrollIntoViewOptions.behavior || "smooth"
43
+ });
44
+ return;
45
+ }
46
+ const top = element.getBoundingClientRect().top + window.scrollY - offset;
47
+ window.scrollTo({
48
+ top,
49
+ behavior: scrollIntoViewOptions.behavior || "smooth"
50
+ });
51
+ return;
52
+ }
53
+ element.scrollIntoView({
54
+ behavior: "smooth",
55
+ block: "start",
56
+ inline: "start",
57
+ ...scrollIntoViewOptions
58
+ });
59
+ }, [])
35
60
  };
36
61
  };
37
62
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-scroll-to-elements.mjs","names":[],"sources":["../../src/hooks/use-scroll-to-elements.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\ninterface Options extends ScrollIntoViewOptions {\n offset?: number;\n}\n\nconst useScrollToElements = (options?: Options) => {\n const elementRefs = useRef<(HTMLElement | null)[]>([]);\n // `options` is almost always an inline object literal at the call site,\n // so depending on it directly would recreate `scrollToElement` on every\n // render — read the latest value from a ref instead.\n const optionsRef = useRef(options);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n const scrollToElement = useCallback((index: number) => {\n const element = elementRefs.current[index];\n\n if (!element) {\n return;\n }\n\n const { offset, ...scrollIntoViewOptions } = optionsRef.current ?? {};\n\n // With an offset, `scrollIntoView` and `scrollTo` would both animate\n // the same scroll, fighting each other and settling in the wrong\n // place — use `scrollTo` alone, computed from the element's position\n // before any scrolling starts (not after `scrollIntoView`, where the\n // rect would reflect a mid-animation position).\n if (offset != null) {\n const top = element.getBoundingClientRect().top + window.scrollY - offset;\n\n window.scrollTo({\n top,\n behavior: scrollIntoViewOptions.behavior || 'smooth',\n });\n return;\n }\n\n element.scrollIntoView({\n behavior: 'smooth',\n block: 'start',\n inline: 'start',\n ...scrollIntoViewOptions,\n });\n }, []);\n\n const setElementRef = useCallback(\n (element: HTMLElement | null, index: number) => {\n elementRefs.current[index] = element;\n },\n [],\n );\n\n return { elementRefs, setElementRef, scrollToElement };\n};\n\nexport default useScrollToElements;\n"],"mappings":";;;AAMA,MAAM,uBAAuB,YAAsB;CACjD,MAAM,cAAc,OAA+B,EAAE,CAAC;CAItD,MAAM,aAAa,OAAO,QAAQ;AAElC,iBAAgB;AACd,aAAW,UAAU;GACrB;CAEF,MAAM,kBAAkB,aAAa,UAAkB;EACrD,MAAM,UAAU,YAAY,QAAQ;AAEpC,MAAI,CAAC,QACH;EAGF,MAAM,EAAE,QAAQ,GAAG,0BAA0B,WAAW,WAAW,EAAE;AAOrE,MAAI,UAAU,MAAM;GAClB,MAAM,MAAM,QAAQ,uBAAuB,CAAC,MAAM,OAAO,UAAU;AAEnE,UAAO,SAAS;IACd;IACA,UAAU,sBAAsB,YAAY;IAC7C,CAAC;AACF;;AAGF,UAAQ,eAAe;GACrB,UAAU;GACV,OAAO;GACP,QAAQ;GACR,GAAG;GACJ,CAAC;IACD,EAAE,CAAC;AASN,QAAO;EAAE;EAAa,eAPA,aACnB,SAA6B,UAAkB;AAC9C,eAAY,QAAQ,SAAS;KAE/B,EAAE,CACH;EAEoC;EAAiB"}
1
+ {"version":3,"file":"use-scroll-to-elements.mjs","names":[],"sources":["../../src/hooks/use-scroll-to-elements.ts"],"sourcesContent":["import { type RefObject, useCallback, useEffect, useRef } from 'react';\n\ntype ElementKey = string;\n\ntype ScrollContainer = HTMLElement | RefObject<HTMLElement | null> | null;\n\ninterface Options extends ScrollIntoViewOptions {\n offset?: number;\n // Element to scroll instead of `window` when `offset` is set — e.g. a\n // modal's own scrollable body. `scrollIntoView` (the no-offset path)\n // already finds its nearest scrollable ancestor on its own and doesn't\n // need this.\n container?: ScrollContainer;\n}\n\nconst resolveContainer = (container: ScrollContainer): HTMLElement | null => {\n if (!container) {\n return null;\n }\n return 'current' in container ? container.current : container;\n};\n\nconst useScrollToElements = (defaultOptions?: Options) => {\n const elementsRef = useRef(new Map<ElementKey, HTMLElement>());\n const registerCallbacksRef = useRef(\n new Map<ElementKey, (node: HTMLElement | null) => void>(),\n );\n\n // `defaultOptions` is almost always an inline object literal at the call\n // site, so depending on it directly would recreate `scrollTo` on every\n // render — read the latest value from a ref instead.\n const defaultOptionsRef = useRef(defaultOptions);\n\n useEffect(() => {\n defaultOptionsRef.current = defaultOptions;\n });\n\n // Returns a stable callback per key so React doesn't detach/reattach the\n // ref (and thrash the Map) on every render — only a genuine unmount\n // (node === null) removes the entry.\n const register = useCallback((key: ElementKey) => {\n let callback = registerCallbacksRef.current.get(key);\n\n if (!callback) {\n callback = (node: HTMLElement | null) => {\n if (node) {\n elementsRef.current.set(key, node);\n } else {\n elementsRef.current.delete(key);\n }\n };\n registerCallbacksRef.current.set(key, callback);\n }\n\n return callback;\n }, []);\n\n const scrollTo = useCallback((key: ElementKey, options?: Options) => {\n const element = elementsRef.current.get(key);\n\n if (!element) {\n return;\n }\n\n const { offset, container, ...scrollIntoViewOptions } = {\n ...defaultOptionsRef.current,\n ...options,\n };\n\n // With an offset, `scrollIntoView` and `scrollTo` would both animate\n // the same scroll, fighting each other and settling in the wrong\n // place — use `scrollTo` alone, computed from the element's position\n // before any scrolling starts (not after `scrollIntoView`, where the\n // rect would reflect a mid-animation position).\n if (offset != null) {\n const containerElement = resolveContainer(container ?? null);\n\n if (containerElement) {\n const containerRect = containerElement.getBoundingClientRect();\n const elementRect = element.getBoundingClientRect();\n const top =\n containerElement.scrollTop +\n (elementRect.top - containerRect.top) -\n offset;\n\n containerElement.scrollTo({\n top,\n behavior: scrollIntoViewOptions.behavior || 'smooth',\n });\n return;\n }\n\n const top = element.getBoundingClientRect().top + window.scrollY - offset;\n\n window.scrollTo({\n top,\n behavior: scrollIntoViewOptions.behavior || 'smooth',\n });\n return;\n }\n\n element.scrollIntoView({\n behavior: 'smooth',\n block: 'start',\n inline: 'start',\n ...scrollIntoViewOptions,\n });\n }, []);\n\n return { register, scrollTo };\n};\n\nexport default useScrollToElements;\n"],"mappings":";;;AAeA,MAAM,oBAAoB,cAAmD;AAC3E,KAAI,CAAC,UACH,QAAO;AAET,QAAO,aAAa,YAAY,UAAU,UAAU;;AAGtD,MAAM,uBAAuB,mBAA6B;CACxD,MAAM,cAAc,uBAAO,IAAI,KAA8B,CAAC;CAC9D,MAAM,uBAAuB,uBAC3B,IAAI,KAAqD,CAC1D;CAKD,MAAM,oBAAoB,OAAO,eAAe;AAEhD,iBAAgB;AACd,oBAAkB,UAAU;GAC5B;AA0EF,QAAO;EAAE,UArEQ,aAAa,QAAoB;GAChD,IAAI,WAAW,qBAAqB,QAAQ,IAAI,IAAI;AAEpD,OAAI,CAAC,UAAU;AACb,gBAAY,SAA6B;AACvC,SAAI,KACF,aAAY,QAAQ,IAAI,KAAK,KAAK;SAElC,aAAY,QAAQ,OAAO,IAAI;;AAGnC,yBAAqB,QAAQ,IAAI,KAAK,SAAS;;AAGjD,UAAO;KACN,EAAE,CAAC;EAsDa,UApDF,aAAa,KAAiB,YAAsB;GACnE,MAAM,UAAU,YAAY,QAAQ,IAAI,IAAI;AAE5C,OAAI,CAAC,QACH;GAGF,MAAM,EAAE,QAAQ,WAAW,GAAG,0BAA0B;IACtD,GAAG,kBAAkB;IACrB,GAAG;IACJ;AAOD,OAAI,UAAU,MAAM;IAClB,MAAM,mBAAmB,iBAAiB,aAAa,KAAK;AAE5D,QAAI,kBAAkB;KACpB,MAAM,gBAAgB,iBAAiB,uBAAuB;KAC9D,MAAM,cAAc,QAAQ,uBAAuB;KACnD,MAAM,MACJ,iBAAiB,aAChB,YAAY,MAAM,cAAc,OACjC;AAEF,sBAAiB,SAAS;MACxB;MACA,UAAU,sBAAsB,YAAY;MAC7C,CAAC;AACF;;IAGF,MAAM,MAAM,QAAQ,uBAAuB,CAAC,MAAM,OAAO,UAAU;AAEnE,WAAO,SAAS;KACd;KACA,UAAU,sBAAsB,YAAY;KAC7C,CAAC;AACF;;AAGF,WAAQ,eAAe;IACrB,UAAU;IACV,OAAO;IACP,QAAQ;IACR,GAAG;IACJ,CAAC;KACD,EAAE,CAAC;EAEuB"}
@@ -1,5 +1,9 @@
1
1
  //#region src/hooks/use-throttle.d.ts
2
- declare const useThrottle: <T>(value: T, delay?: number) => T;
2
+ interface Options {
3
+ leading?: boolean;
4
+ trailing?: boolean;
5
+ }
6
+ declare const useThrottle: <T>(value: T, delay?: number, options?: Options) => T;
3
7
  //#endregion
4
8
  export { useThrottle };
5
9
  //# sourceMappingURL=use-throttle.d.mts.map
@@ -1,39 +1,13 @@
1
- import { useEffect, useRef, useState } from "react";
1
+ import useThrottledCallback from "./use-throttled-callback.mjs";
2
+ import { useEffect, useState } from "react";
2
3
 
3
4
  //#region src/hooks/use-throttle.ts
4
- const useThrottle = (value, delay = 100) => {
5
+ const useThrottle = (value, delay = 100, options = {}) => {
5
6
  const [throttledValue, setThrottledValue] = useState(value);
6
- const lastExecutedRef = useRef(0);
7
- const timeoutRef = useRef(null);
8
- const latestValueRef = useRef(value);
7
+ const throttledSetValue = useThrottledCallback(setThrottledValue, delay, options);
9
8
  useEffect(() => {
10
- latestValueRef.current = value;
11
- }, [value]);
12
- useEffect(() => {
13
- const now = Date.now();
14
- const elapsed = now - lastExecutedRef.current;
15
- if (elapsed >= delay) {
16
- if (timeoutRef.current) {
17
- clearTimeout(timeoutRef.current);
18
- timeoutRef.current = null;
19
- }
20
- lastExecutedRef.current = now;
21
- setThrottledValue(latestValueRef.current);
22
- return;
23
- }
24
- const remaining = delay - elapsed;
25
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
26
- timeoutRef.current = setTimeout(() => {
27
- lastExecutedRef.current = Date.now();
28
- setThrottledValue(latestValueRef.current);
29
- }, remaining);
30
- return () => {
31
- if (timeoutRef.current) {
32
- clearTimeout(timeoutRef.current);
33
- timeoutRef.current = null;
34
- }
35
- };
36
- }, [value, delay]);
9
+ throttledSetValue(value);
10
+ }, [value, throttledSetValue]);
37
11
  return throttledValue;
38
12
  };
39
13
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-throttle.mjs","names":[],"sources":["../../src/hooks/use-throttle.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\nconst useThrottle = <T>(value: T, delay = 100): T => {\n const [throttledValue, setThrottledValue] = useState(value);\n const lastExecutedRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestValueRef = useRef(value);\n\n useEffect(() => {\n latestValueRef.current = value;\n }, [value]);\n\n useEffect(() => {\n const now = Date.now();\n const elapsed = now - lastExecutedRef.current;\n\n if (elapsed >= delay) {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n lastExecutedRef.current = now;\n setThrottledValue(latestValueRef.current);\n return;\n }\n\n const remaining = delay - elapsed;\n\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n lastExecutedRef.current = Date.now();\n setThrottledValue(latestValueRef.current);\n }, remaining);\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [value, delay]);\n\n return throttledValue;\n};\n\nexport default useThrottle;\n"],"mappings":";;;AAEA,MAAM,eAAkB,OAAU,QAAQ,QAAW;CACnD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,kBAAkB,OAAO,EAAE;CACjC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,iBAAiB,OAAO,MAAM;AAEpC,iBAAgB;AACd,iBAAe,UAAU;IACxB,CAAC,MAAM,CAAC;AAEX,iBAAgB;EACd,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,UAAU,MAAM,gBAAgB;AAEtC,MAAI,WAAW,OAAO;AACpB,OAAI,WAAW,SAAS;AACtB,iBAAa,WAAW,QAAQ;AAChC,eAAW,UAAU;;AAGvB,mBAAgB,UAAU;AAC1B,qBAAkB,eAAe,QAAQ;AACzC;;EAGF,MAAM,YAAY,QAAQ;AAE1B,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAGlC,aAAW,UAAU,iBAAiB;AACpC,mBAAgB,UAAU,KAAK,KAAK;AACpC,qBAAkB,eAAe,QAAQ;KACxC,UAAU;AAEb,eAAa;AACX,OAAI,WAAW,SAAS;AACtB,iBAAa,WAAW,QAAQ;AAChC,eAAW,UAAU;;;IAGxB,CAAC,OAAO,MAAM,CAAC;AAElB,QAAO"}
1
+ {"version":3,"file":"use-throttle.mjs","names":[],"sources":["../../src/hooks/use-throttle.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport useThrottledCallback from './use-throttled-callback';\n\ninterface Options {\n leading?: boolean;\n trailing?: boolean;\n}\n\nconst useThrottle = <T>(value: T, delay = 100, options: Options = {}): T => {\n const [throttledValue, setThrottledValue] = useState(value);\n\n const throttledSetValue = useThrottledCallback(\n setThrottledValue,\n delay,\n options,\n );\n\n useEffect(() => {\n throttledSetValue(value);\n // `throttledSetValue`'s identity is stable for the lifetime of the\n // component (see useThrottledCallback), so listing it here doesn't\n // cause any extra re-runs beyond `value` actually changing.\n }, [value, throttledSetValue]);\n\n return throttledValue;\n};\n\nexport default useThrottle;\n"],"mappings":";;;;AASA,MAAM,eAAkB,OAAU,QAAQ,KAAK,UAAmB,EAAE,KAAQ;CAC1E,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAE3D,MAAM,oBAAoB,qBACxB,mBACA,OACA,QACD;AAED,iBAAgB;AACd,oBAAkB,MAAM;IAIvB,CAAC,OAAO,kBAAkB,CAAC;AAE9B,QAAO"}
@@ -0,0 +1,12 @@
1
+ //#region src/hooks/use-throttled-callback.d.ts
2
+ interface Options {
3
+ leading?: boolean;
4
+ trailing?: boolean;
5
+ }
6
+ declare const useThrottledCallback: <Args extends unknown[]>(callback: (...args: Args) => void, delay?: number, {
7
+ leading,
8
+ trailing
9
+ }?: Options) => (...args: Args) => void;
10
+ //#endregion
11
+ export { useThrottledCallback };
12
+ //# sourceMappingURL=use-throttled-callback.d.mts.map
@@ -0,0 +1,54 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/use-throttled-callback.ts
4
+ const useThrottledCallback = (callback, delay = 100, { leading = true, trailing = true } = {}) => {
5
+ const callbackRef = useRef(callback);
6
+ const delayRef = useRef(delay);
7
+ const leadingRef = useRef(leading);
8
+ const trailingRef = useRef(trailing);
9
+ useEffect(() => {
10
+ callbackRef.current = callback;
11
+ delayRef.current = delay;
12
+ leadingRef.current = leading;
13
+ trailingRef.current = trailing;
14
+ });
15
+ const lastExecutedRef = useRef(null);
16
+ const timeoutRef = useRef(null);
17
+ const lastArgsRef = useRef(null);
18
+ const stableThrottledCallback = useRef(null);
19
+ if (!stableThrottledCallback.current) {
20
+ const invoke = () => {
21
+ lastExecutedRef.current = Date.now();
22
+ timeoutRef.current = null;
23
+ if (lastArgsRef.current) callbackRef.current(...lastArgsRef.current);
24
+ };
25
+ stableThrottledCallback.current = (...args) => {
26
+ lastArgsRef.current = args;
27
+ const now = Date.now();
28
+ const elapsed = lastExecutedRef.current === null ? Infinity : now - lastExecutedRef.current;
29
+ if (elapsed >= delayRef.current) {
30
+ if (timeoutRef.current) {
31
+ clearTimeout(timeoutRef.current);
32
+ timeoutRef.current = null;
33
+ }
34
+ if (leadingRef.current) invoke();
35
+ else {
36
+ lastExecutedRef.current = now;
37
+ if (trailingRef.current) timeoutRef.current = setTimeout(invoke, delayRef.current);
38
+ }
39
+ return;
40
+ }
41
+ if (trailingRef.current && !timeoutRef.current) timeoutRef.current = setTimeout(invoke, delayRef.current - elapsed);
42
+ };
43
+ }
44
+ useEffect(() => {
45
+ return () => {
46
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
47
+ };
48
+ }, []);
49
+ return stableThrottledCallback.current;
50
+ };
51
+
52
+ //#endregion
53
+ export { useThrottledCallback as default };
54
+ //# sourceMappingURL=use-throttled-callback.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-throttled-callback.mjs","names":[],"sources":["../../src/hooks/use-throttled-callback.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface Options {\n leading?: boolean;\n trailing?: boolean;\n}\n\n// Invokes `callback` at most once per `delay`ms, regardless of how often\n// the returned function is called — the callback-shaped counterpart to\n// useThrottle (which throttles a value). `leading` fires on the first call\n// of a burst immediately; `trailing` schedules one more call (with\n// whatever args arrived last) at the end of the window if calls kept\n// coming. Both default to true, matching useThrottle's fixed behavior.\nconst useThrottledCallback = <Args extends unknown[]>(\n callback: (...args: Args) => void,\n delay = 100,\n { leading = true, trailing = true }: Options = {},\n) => {\n const callbackRef = useRef(callback);\n const delayRef = useRef(delay);\n const leadingRef = useRef(leading);\n const trailingRef = useRef(trailing);\n\n useEffect(() => {\n callbackRef.current = callback;\n delayRef.current = delay;\n leadingRef.current = leading;\n trailingRef.current = trailing;\n });\n\n const lastExecutedRef = useRef<number | null>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastArgsRef = useRef<Args | null>(null);\n\n const stableThrottledCallback = useRef<((...args: Args) => void) | null>(\n null,\n );\n\n if (!stableThrottledCallback.current) {\n const invoke = () => {\n lastExecutedRef.current = Date.now();\n timeoutRef.current = null;\n\n if (lastArgsRef.current) {\n callbackRef.current(...lastArgsRef.current);\n }\n };\n\n stableThrottledCallback.current = (...args: Args) => {\n lastArgsRef.current = args;\n\n const now = Date.now();\n const elapsed =\n lastExecutedRef.current === null\n ? Infinity\n : now - lastExecutedRef.current;\n\n if (elapsed >= delayRef.current) {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n if (leadingRef.current) {\n invoke();\n } else {\n lastExecutedRef.current = now;\n\n if (trailingRef.current) {\n timeoutRef.current = setTimeout(invoke, delayRef.current);\n }\n }\n return;\n }\n\n if (trailingRef.current && !timeoutRef.current) {\n timeoutRef.current = setTimeout(invoke, delayRef.current - elapsed);\n }\n };\n }\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return stableThrottledCallback.current;\n};\n\nexport default useThrottledCallback;\n"],"mappings":";;;AAaA,MAAM,wBACJ,UACA,QAAQ,KACR,EAAE,UAAU,MAAM,WAAW,SAAkB,EAAE,KAC9C;CACH,MAAM,cAAc,OAAO,SAAS;CACpC,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,aAAa,OAAO,QAAQ;CAClC,MAAM,cAAc,OAAO,SAAS;AAEpC,iBAAgB;AACd,cAAY,UAAU;AACtB,WAAS,UAAU;AACnB,aAAW,UAAU;AACrB,cAAY,UAAU;GACtB;CAEF,MAAM,kBAAkB,OAAsB,KAAK;CACnD,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAoB,KAAK;CAE7C,MAAM,0BAA0B,OAC9B,KACD;AAED,KAAI,CAAC,wBAAwB,SAAS;EACpC,MAAM,eAAe;AACnB,mBAAgB,UAAU,KAAK,KAAK;AACpC,cAAW,UAAU;AAErB,OAAI,YAAY,QACd,aAAY,QAAQ,GAAG,YAAY,QAAQ;;AAI/C,0BAAwB,WAAW,GAAG,SAAe;AACnD,eAAY,UAAU;GAEtB,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,UACJ,gBAAgB,YAAY,OACxB,WACA,MAAM,gBAAgB;AAE5B,OAAI,WAAW,SAAS,SAAS;AAC/B,QAAI,WAAW,SAAS;AACtB,kBAAa,WAAW,QAAQ;AAChC,gBAAW,UAAU;;AAGvB,QAAI,WAAW,QACb,SAAQ;SACH;AACL,qBAAgB,UAAU;AAE1B,SAAI,YAAY,QACd,YAAW,UAAU,WAAW,QAAQ,SAAS,QAAQ;;AAG7D;;AAGF,OAAI,YAAY,WAAW,CAAC,WAAW,QACrC,YAAW,UAAU,WAAW,QAAQ,SAAS,UAAU,QAAQ;;;AAKzE,iBAAgB;AACd,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;AAEN,QAAO,wBAAwB"}
@@ -0,0 +1,8 @@
1
+ //#region src/hooks/use-timeout.d.ts
2
+ declare const useTimeout: (callback: () => void, delay: number | null) => {
3
+ reset: () => void;
4
+ clear: () => void;
5
+ };
6
+ //#endregion
7
+ export { useTimeout };
8
+ //# sourceMappingURL=use-timeout.d.mts.map
@@ -0,0 +1,38 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/use-timeout.ts
4
+ const useTimeout = (callback, delay) => {
5
+ const callbackRef = useRef(callback);
6
+ const delayRef = useRef(delay);
7
+ const timeoutRef = useRef(null);
8
+ useEffect(() => {
9
+ callbackRef.current = callback;
10
+ delayRef.current = delay;
11
+ });
12
+ const clear = useCallback(() => {
13
+ if (timeoutRef.current !== null) {
14
+ clearTimeout(timeoutRef.current);
15
+ timeoutRef.current = null;
16
+ }
17
+ }, []);
18
+ const reset = useCallback(() => {
19
+ clear();
20
+ if (delayRef.current === null) return;
21
+ timeoutRef.current = setTimeout(() => {
22
+ timeoutRef.current = null;
23
+ callbackRef.current();
24
+ }, delayRef.current);
25
+ }, [clear]);
26
+ useEffect(() => {
27
+ reset();
28
+ return clear;
29
+ }, [delay]);
30
+ return {
31
+ reset,
32
+ clear
33
+ };
34
+ };
35
+
36
+ //#endregion
37
+ export { useTimeout as default };
38
+ //# sourceMappingURL=use-timeout.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-timeout.mjs","names":[],"sources":["../../src/hooks/use-timeout.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\n// `delay === null` means inactive (no timer scheduled) — `0` is a valid,\n// distinct delay, same rule useRecursiveTimeout uses. A bare `if (!delay)`\n// guard (as opposed to `if (delay === null)`) is the classic bug this\n// hook exists to avoid: it silently no-ops for a delay of 0 too.\nconst useTimeout = (callback: () => void, delay: number | null) => {\n const callbackRef = useRef(callback);\n const delayRef = useRef(delay);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n callbackRef.current = callback;\n delayRef.current = delay;\n });\n\n const clear = useCallback(() => {\n if (timeoutRef.current !== null) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n }, []);\n\n // Restarts the timer from now, using whatever `delay` is current —\n // e.g. a toast's auto-dismiss timer that should start over each time\n // the user hovers away from it.\n const reset = useCallback(() => {\n clear();\n\n if (delayRef.current === null) {\n return;\n }\n\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n callbackRef.current();\n }, delayRef.current);\n }, [clear]);\n\n useEffect(() => {\n reset();\n return clear;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [delay]);\n\n return { reset, clear };\n};\n\nexport default useTimeout;\n"],"mappings":";;;AAMA,MAAM,cAAc,UAAsB,UAAyB;CACjE,MAAM,cAAc,OAAO,SAAS;CACpC,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,aAAa,OAA6C,KAAK;AAErE,iBAAgB;AACd,cAAY,UAAU;AACtB,WAAS,UAAU;GACnB;CAEF,MAAM,QAAQ,kBAAkB;AAC9B,MAAI,WAAW,YAAY,MAAM;AAC/B,gBAAa,WAAW,QAAQ;AAChC,cAAW,UAAU;;IAEtB,EAAE,CAAC;CAKN,MAAM,QAAQ,kBAAkB;AAC9B,SAAO;AAEP,MAAI,SAAS,YAAY,KACvB;AAGF,aAAW,UAAU,iBAAiB;AACpC,cAAW,UAAU;AACrB,eAAY,SAAS;KACpB,SAAS,QAAQ;IACnB,CAAC,MAAM,CAAC;AAEX,iBAAgB;AACd,SAAO;AACP,SAAO;IAEN,CAAC,MAAM,CAAC;AAEX,QAAO;EAAE;EAAO;EAAO"}
@@ -0,0 +1,5 @@
1
+ //#region src/hooks/use-toggle.d.ts
2
+ declare const useToggle: (initial?: boolean) => [boolean, () => void, (value: boolean) => void];
3
+ //#endregion
4
+ export { useToggle };
5
+ //# sourceMappingURL=use-toggle.d.mts.map
@@ -0,0 +1,17 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ //#region src/hooks/use-toggle.ts
4
+ const useToggle = (initial = false) => {
5
+ const [value, setValue] = useState(initial);
6
+ return [
7
+ value,
8
+ useCallback(() => {
9
+ setValue((prev) => !prev);
10
+ }, []),
11
+ setValue
12
+ ];
13
+ };
14
+
15
+ //#endregion
16
+ export { useToggle as default };
17
+ //# sourceMappingURL=use-toggle.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-toggle.mjs","names":[],"sources":["../../src/hooks/use-toggle.ts"],"sourcesContent":["import { useCallback, useState } from 'react';\n\nconst useToggle = (\n initial = false,\n): [boolean, () => void, (value: boolean) => void] => {\n const [value, setValue] = useState(initial);\n\n const toggle = useCallback(() => {\n setValue(prev => !prev);\n }, []);\n\n return [value, toggle, setValue];\n};\n\nexport default useToggle;\n"],"mappings":";;;AAEA,MAAM,aACJ,UAAU,UAC0C;CACpD,MAAM,CAAC,OAAO,YAAY,SAAS,QAAQ;AAM3C,QAAO;EAAC;EAJO,kBAAkB;AAC/B,aAAS,SAAQ,CAAC,KAAK;KACtB,EAAE,CAAC;EAEiB;EAAS"}