@jbpark/use-hooks 2.10.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 (74) 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-controllable-state.d.mts +4 -5
  7. package/dist/hooks/use-controllable-state.mjs +10 -4
  8. package/dist/hooks/use-controllable-state.mjs.map +1 -1
  9. package/dist/hooks/use-debounced-value.d.mts +5 -0
  10. package/dist/hooks/use-debounced-value.mjs +20 -0
  11. package/dist/hooks/use-debounced-value.mjs.map +1 -0
  12. package/dist/hooks/use-element-position.mjs +37 -8
  13. package/dist/hooks/use-element-position.mjs.map +1 -1
  14. package/dist/hooks/use-element-scroll.d.mts +6 -1
  15. package/dist/hooks/use-element-scroll.mjs +30 -8
  16. package/dist/hooks/use-element-scroll.mjs.map +1 -1
  17. package/dist/hooks/use-event-listener.d.mts +21 -0
  18. package/dist/hooks/use-event-listener.mjs +37 -0
  19. package/dist/hooks/use-event-listener.mjs.map +1 -0
  20. package/dist/hooks/use-file-drop.d.mts +14 -0
  21. package/dist/hooks/use-file-drop.mjs +85 -0
  22. package/dist/hooks/use-file-drop.mjs.map +1 -0
  23. package/dist/hooks/use-image.d.mts +2 -1
  24. package/dist/hooks/use-image.mjs +4 -3
  25. package/dist/hooks/use-image.mjs.map +1 -1
  26. package/dist/hooks/use-intersection-observer.d.mts +8 -1
  27. package/dist/hooks/use-intersection-observer.mjs +37 -5
  28. package/dist/hooks/use-intersection-observer.mjs.map +1 -1
  29. package/dist/hooks/use-interval.d.mts +5 -0
  30. package/dist/hooks/use-interval.mjs +20 -0
  31. package/dist/hooks/use-interval.mjs.map +1 -0
  32. package/dist/hooks/use-key-press.d.mts +14 -0
  33. package/dist/hooks/use-key-press.mjs +63 -0
  34. package/dist/hooks/use-key-press.mjs.map +1 -0
  35. package/dist/hooks/use-merged-ref.d.mts +8 -0
  36. package/dist/hooks/use-merged-ref.mjs +25 -0
  37. package/dist/hooks/use-merged-ref.mjs.map +1 -0
  38. package/dist/hooks/use-multi-select.mjs +14 -5
  39. package/dist/hooks/use-multi-select.mjs.map +1 -1
  40. package/dist/hooks/use-mutation-observer.d.mts +11 -0
  41. package/dist/hooks/use-mutation-observer.mjs +33 -0
  42. package/dist/hooks/use-mutation-observer.mjs.map +1 -0
  43. package/dist/hooks/use-previous.d.mts +5 -0
  44. package/dist/hooks/use-previous.mjs +14 -0
  45. package/dist/hooks/use-previous.mjs.map +1 -0
  46. package/dist/hooks/use-resize-observer.d.mts +12 -0
  47. package/dist/hooks/use-resize-observer.mjs +29 -0
  48. package/dist/hooks/use-resize-observer.mjs.map +1 -0
  49. package/dist/hooks/use-responsive-size.d.mts +1 -0
  50. package/dist/hooks/use-responsive-size.mjs +70 -54
  51. package/dist/hooks/use-responsive-size.mjs.map +1 -1
  52. package/dist/hooks/use-scroll-to-elements.d.mts +7 -5
  53. package/dist/hooks/use-scroll-to-elements.mjs +52 -27
  54. package/dist/hooks/use-scroll-to-elements.mjs.map +1 -1
  55. package/dist/hooks/use-throttle.d.mts +5 -1
  56. package/dist/hooks/use-throttle.mjs +6 -32
  57. package/dist/hooks/use-throttle.mjs.map +1 -1
  58. package/dist/hooks/use-throttled-callback.d.mts +12 -0
  59. package/dist/hooks/use-throttled-callback.mjs +54 -0
  60. package/dist/hooks/use-throttled-callback.mjs.map +1 -0
  61. package/dist/hooks/use-timeout.d.mts +8 -0
  62. package/dist/hooks/use-timeout.mjs +38 -0
  63. package/dist/hooks/use-timeout.mjs.map +1 -0
  64. package/dist/hooks/use-toggle.d.mts +5 -0
  65. package/dist/hooks/use-toggle.mjs +17 -0
  66. package/dist/hooks/use-toggle.mjs.map +1 -0
  67. package/dist/hooks/use-window-scroll.mjs +60 -38
  68. package/dist/hooks/use-window-scroll.mjs.map +1 -1
  69. package/dist/index.d.mts +13 -2
  70. package/dist/index.mjs +13 -2
  71. package/package.json +1 -1
  72. package/dist/hooks/use-timeline.d.mts +0 -50
  73. package/dist/hooks/use-timeline.mjs +0 -176
  74. package/dist/hooks/use-timeline.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import useDebounce from "./use-debounce.mjs";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
 
4
4
  //#region src/hooks/use-responsive-size.ts
5
5
  const BREAKPOINTS = {
@@ -28,76 +28,92 @@ const getBreakpointInfo = (width) => {
28
28
  "2xl": width >= BREAKPOINTS["2xl"]
29
29
  };
30
30
  };
31
+ const breakpointEqual = (a, b) => a.current === b.current && a.xs === b.xs && a.sm === b.sm && a.md === b.md && a.lg === b.lg && a.xl === b.xl && a["2xl"] === b["2xl"];
32
+ const measureTarget = (target) => ({
33
+ width: target.offsetWidth,
34
+ height: target.offsetHeight
35
+ });
36
+ const measureViewport = () => ({
37
+ width: window.innerWidth,
38
+ height: window.innerHeight
39
+ });
31
40
  const useResponsiveSize = (options) => {
32
- const { delay = 100, container } = options || {};
41
+ const { delay = 100, container, viewport = false } = options || {};
42
+ const [element, setElement] = useState(null);
33
43
  const [size, setSize] = useState({
34
44
  width: 0,
35
45
  height: 0
36
46
  });
37
- const [breakpoint, setBreakpoint] = useState({
38
- current: "xs",
39
- xs: true,
40
- sm: false,
41
- md: false,
42
- lg: false,
43
- xl: false,
44
- "2xl": false
47
+ const [breakpoint, setBreakpoint] = useState(() => getBreakpointInfo(0));
48
+ const observerRef = useRef(null);
49
+ const latestRef = useRef({
50
+ width: 0,
51
+ height: 0
45
52
  });
46
- const [debouncedSize, setDebouncedSize] = useState({
53
+ const committedRef = useRef({
47
54
  width: 0,
48
55
  height: 0
49
56
  });
50
- const [element, setElement] = useState(null);
51
- const observerRef = useRef(null);
52
57
  const ref = useCallback((node) => {
53
58
  setElement(node);
54
59
  }, []);
55
- useDebounce(() => {
56
- setDebouncedSize(size);
57
- }, { delay }, [size]);
58
- useEffect(() => {
59
- const updateSize = () => {
60
- const target = container ?? element ?? document.body;
61
- if (!target) return;
62
- const { offsetWidth, offsetHeight } = target;
63
- setSize((prev) => {
64
- if (prev.width !== offsetWidth || prev.height !== offsetHeight) return {
65
- width: offsetWidth,
66
- height: offsetHeight
67
- };
68
- return prev;
69
- });
70
- setBreakpoint((prev) => {
71
- const next = getBreakpointInfo(offsetWidth);
72
- if (prev.current !== next.current) return next;
73
- return prev;
74
- });
75
- };
76
- const connect = () => {
60
+ const commit = useCallback(() => {
61
+ const next = latestRef.current;
62
+ if (committedRef.current.width === next.width && committedRef.current.height === next.height) return;
63
+ committedRef.current = next;
64
+ setSize(next);
65
+ const nextBreakpoint = getBreakpointInfo(next.width);
66
+ setBreakpoint((prev) => breakpointEqual(prev, nextBreakpoint) ? prev : nextBreakpoint);
67
+ }, []);
68
+ const debouncedCommit = useDebounce(commit, {
69
+ delay,
70
+ autoInvoke: false
71
+ });
72
+ useLayoutEffect(() => {
73
+ const measured = viewport ? measureViewport() : (() => {
77
74
  const target = container ?? element ?? document.body;
78
- if (!target) return;
79
- updateSize();
80
- if (observerRef.current) observerRef.current.disconnect();
81
- observerRef.current = new ResizeObserver(() => {
82
- requestAnimationFrame(() => {
83
- updateSize();
84
- });
75
+ return target ? measureTarget(target) : null;
76
+ })();
77
+ if (!measured) return;
78
+ latestRef.current = measured;
79
+ commit();
80
+ }, [
81
+ container,
82
+ element,
83
+ viewport,
84
+ commit
85
+ ]);
86
+ useEffect(() => {
87
+ if (viewport) {
88
+ const onResize = () => {
89
+ latestRef.current = measureViewport();
90
+ debouncedCommit();
91
+ };
92
+ window.addEventListener("resize", onResize);
93
+ return () => window.removeEventListener("resize", onResize);
94
+ }
95
+ const target = container ?? element ?? document.body;
96
+ if (!target) return;
97
+ if (observerRef.current) observerRef.current.disconnect();
98
+ observerRef.current = new ResizeObserver(() => {
99
+ requestAnimationFrame(() => {
100
+ latestRef.current = measureTarget(target);
101
+ debouncedCommit();
85
102
  });
86
- observerRef.current.observe(target);
87
- };
88
- const disconnect = () => {
89
- if (observerRef.current) {
90
- observerRef.current.disconnect();
91
- observerRef.current = null;
92
- }
93
- };
94
- connect();
103
+ });
104
+ observerRef.current.observe(target);
95
105
  return () => {
96
- disconnect();
106
+ observerRef.current?.disconnect();
107
+ observerRef.current = null;
97
108
  };
98
- }, [container, element]);
109
+ }, [
110
+ container,
111
+ element,
112
+ viewport,
113
+ debouncedCommit
114
+ ]);
99
115
  return {
100
- size: debouncedSize,
116
+ size,
101
117
  breakpoint,
102
118
  ref
103
119
  };
@@ -1 +1 @@
1
- {"version":3,"file":"use-responsive-size.mjs","names":[],"sources":["../../src/hooks/use-responsive-size.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport useDebounce from './use-debounce';\n\ntype Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ninterface BreakpointInfo {\n current: Breakpoint;\n xs: boolean;\n sm: boolean;\n md: boolean;\n lg: boolean;\n xl: boolean;\n '2xl': boolean;\n}\n\ninterface Options {\n delay?: number;\n container?: HTMLElement | null;\n}\n\nconst BREAKPOINTS = {\n xs: 0, // < 640px\n sm: 640, // >= 640px\n md: 768, // >= 768px\n lg: 1024, // >= 1024px\n xl: 1280, // >= 1280px\n '2xl': 1536, // >= 1536px\n} as const;\n\nconst getBreakpointInfo = (width: number): BreakpointInfo => {\n let current: Breakpoint = 'xs';\n\n if (width >= BREAKPOINTS['2xl']) {\n current = '2xl';\n } else if (width >= BREAKPOINTS.xl) {\n current = 'xl';\n } else if (width >= BREAKPOINTS.lg) {\n current = 'lg';\n } else if (width >= BREAKPOINTS.md) {\n current = 'md';\n } else if (width >= BREAKPOINTS.sm) {\n current = 'sm';\n } else {\n current = 'xs';\n }\n\n return {\n current,\n xs: width < BREAKPOINTS.sm,\n sm: width >= BREAKPOINTS.sm && width < BREAKPOINTS.md,\n md: width >= BREAKPOINTS.md && width < BREAKPOINTS.lg,\n lg: width >= BREAKPOINTS.lg && width < BREAKPOINTS.xl,\n xl: width >= BREAKPOINTS.xl && width < BREAKPOINTS['2xl'],\n '2xl': width >= BREAKPOINTS['2xl'],\n };\n};\n\nconst useResponsiveSize = <T extends HTMLElement>(options?: Options) => {\n const { delay = 100, container } = options || {};\n const [size, setSize] = useState({ width: 0, height: 0 });\n const [breakpoint, setBreakpoint] = useState<BreakpointInfo>({\n current: 'xs',\n xs: true,\n sm: false,\n md: false,\n lg: false,\n xl: false,\n '2xl': false,\n });\n\n const [debouncedSize, setDebouncedSize] = useState({ width: 0, height: 0 });\n\n const [element, setElement] = useState<T | null>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n const ref = useCallback((node: T | null) => {\n setElement(node);\n }, []);\n\n useDebounce(\n () => {\n setDebouncedSize(size);\n },\n { delay },\n [size],\n );\n\n useEffect(() => {\n const updateSize = () => {\n const target = container ?? element ?? document.body;\n\n if (!target) {\n return;\n }\n\n const { offsetWidth, offsetHeight } = target;\n\n setSize(prev => {\n if (prev.width !== offsetWidth || prev.height !== offsetHeight) {\n return { width: offsetWidth, height: offsetHeight };\n }\n return prev;\n });\n\n setBreakpoint(prev => {\n const next = getBreakpointInfo(offsetWidth);\n if (prev.current !== next.current) {\n return next;\n }\n return prev;\n });\n };\n\n const connect = () => {\n const target = container ?? element ?? document.body;\n\n if (!target) {\n return;\n }\n\n updateSize();\n\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n\n observerRef.current = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n updateSize();\n });\n });\n\n observerRef.current.observe(target);\n };\n\n const disconnect = () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n observerRef.current = null;\n }\n };\n\n connect();\n\n return () => {\n disconnect();\n };\n }, [container, element]);\n\n return {\n size: debouncedSize,\n breakpoint,\n ref,\n };\n};\n\nexport default useResponsiveSize;\n"],"mappings":";;;;AAqBA,MAAM,cAAc;CAClB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACR;AAED,MAAM,qBAAqB,UAAkC;CAC3D,IAAI,UAAsB;AAE1B,KAAI,SAAS,YAAY,OACvB,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;KAEV,WAAU;AAGZ,QAAO;EACL;EACA,IAAI,QAAQ,YAAY;EACxB,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,OAAO,SAAS,YAAY;EAC7B;;AAGH,MAAM,qBAA4C,YAAsB;CACtE,MAAM,EAAE,QAAQ,KAAK,cAAc,WAAW,EAAE;CAChD,MAAM,CAAC,MAAM,WAAW,SAAS;EAAE,OAAO;EAAG,QAAQ;EAAG,CAAC;CACzD,MAAM,CAAC,YAAY,iBAAiB,SAAyB;EAC3D,SAAS;EACT,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,OAAO;EACR,CAAC;CAEF,MAAM,CAAC,eAAe,oBAAoB,SAAS;EAAE,OAAO;EAAG,QAAQ;EAAG,CAAC;CAE3E,MAAM,CAAC,SAAS,cAAc,SAAmB,KAAK;CACtD,MAAM,cAAc,OAA8B,KAAK;CAEvD,MAAM,MAAM,aAAa,SAAmB;AAC1C,aAAW,KAAK;IACf,EAAE,CAAC;AAEN,mBACQ;AACJ,mBAAiB,KAAK;IAExB,EAAE,OAAO,EACT,CAAC,KAAK,CACP;AAED,iBAAgB;EACd,MAAM,mBAAmB;GACvB,MAAM,SAAS,aAAa,WAAW,SAAS;AAEhD,OAAI,CAAC,OACH;GAGF,MAAM,EAAE,aAAa,iBAAiB;AAEtC,YAAQ,SAAQ;AACd,QAAI,KAAK,UAAU,eAAe,KAAK,WAAW,aAChD,QAAO;KAAE,OAAO;KAAa,QAAQ;KAAc;AAErD,WAAO;KACP;AAEF,kBAAc,SAAQ;IACpB,MAAM,OAAO,kBAAkB,YAAY;AAC3C,QAAI,KAAK,YAAY,KAAK,QACxB,QAAO;AAET,WAAO;KACP;;EAGJ,MAAM,gBAAgB;GACpB,MAAM,SAAS,aAAa,WAAW,SAAS;AAEhD,OAAI,CAAC,OACH;AAGF,eAAY;AAEZ,OAAI,YAAY,QACd,aAAY,QAAQ,YAAY;AAGlC,eAAY,UAAU,IAAI,qBAAqB;AAC7C,gCAA4B;AAC1B,iBAAY;MACZ;KACF;AAEF,eAAY,QAAQ,QAAQ,OAAO;;EAGrC,MAAM,mBAAmB;AACvB,OAAI,YAAY,SAAS;AACvB,gBAAY,QAAQ,YAAY;AAChC,gBAAY,UAAU;;;AAI1B,WAAS;AAET,eAAa;AACX,eAAY;;IAEb,CAAC,WAAW,QAAQ,CAAC;AAExB,QAAO;EACL,MAAM;EACN;EACA;EACD"}
1
+ {"version":3,"file":"use-responsive-size.mjs","names":[],"sources":["../../src/hooks/use-responsive-size.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nimport useDebounce from './use-debounce';\n\ntype Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ninterface BreakpointInfo {\n current: Breakpoint;\n xs: boolean;\n sm: boolean;\n md: boolean;\n lg: boolean;\n xl: boolean;\n '2xl': boolean;\n}\n\ninterface Options {\n delay?: number;\n container?: HTMLElement | null;\n // Measure the viewport (window.innerWidth/innerHeight) instead of an\n // element/document.body. Useful when no `container`/ref is attached and\n // the breakpoint should reflect the viewport rather than document.body's\n // box, which can diverge from it if body has margin/transform.\n viewport?: boolean;\n}\n\nconst BREAKPOINTS = {\n xs: 0, // < 640px\n sm: 640, // >= 640px\n md: 768, // >= 768px\n lg: 1024, // >= 1024px\n xl: 1280, // >= 1280px\n '2xl': 1536, // >= 1536px\n} as const;\n\nconst getBreakpointInfo = (width: number): BreakpointInfo => {\n let current: Breakpoint = 'xs';\n\n if (width >= BREAKPOINTS['2xl']) {\n current = '2xl';\n } else if (width >= BREAKPOINTS.xl) {\n current = 'xl';\n } else if (width >= BREAKPOINTS.lg) {\n current = 'lg';\n } else if (width >= BREAKPOINTS.md) {\n current = 'md';\n } else if (width >= BREAKPOINTS.sm) {\n current = 'sm';\n } else {\n current = 'xs';\n }\n\n return {\n current,\n xs: width < BREAKPOINTS.sm,\n sm: width >= BREAKPOINTS.sm && width < BREAKPOINTS.md,\n md: width >= BREAKPOINTS.md && width < BREAKPOINTS.lg,\n lg: width >= BREAKPOINTS.lg && width < BREAKPOINTS.xl,\n xl: width >= BREAKPOINTS.xl && width < BREAKPOINTS['2xl'],\n '2xl': width >= BREAKPOINTS['2xl'],\n };\n};\n\nconst breakpointEqual = (a: BreakpointInfo, b: BreakpointInfo) =>\n a.current === b.current &&\n a.xs === b.xs &&\n a.sm === b.sm &&\n a.md === b.md &&\n a.lg === b.lg &&\n a.xl === b.xl &&\n a['2xl'] === b['2xl'];\n\nconst measureTarget = (target: HTMLElement) => ({\n width: target.offsetWidth,\n height: target.offsetHeight,\n});\n\nconst measureViewport = () => ({\n width: window.innerWidth,\n height: window.innerHeight,\n});\n\nconst useResponsiveSize = <T extends HTMLElement>(options?: Options) => {\n const { delay = 100, container, viewport = false } = options || {};\n\n const [element, setElement] = useState<T | null>(null);\n const [size, setSize] = useState({ width: 0, height: 0 });\n const [breakpoint, setBreakpoint] = useState<BreakpointInfo>(() =>\n getBreakpointInfo(0),\n );\n\n const observerRef = useRef<ResizeObserver | null>(null);\n const latestRef = useRef({ width: 0, height: 0 });\n const committedRef = useRef({ width: 0, height: 0 });\n\n const ref = useCallback((node: T | null) => {\n setElement(node);\n }, []);\n\n // `size` and `breakpoint` are always committed together here, so\n // consumers never observe one reflecting a newer measurement than the\n // other.\n const commit = useCallback(() => {\n const next = latestRef.current;\n\n if (\n committedRef.current.width === next.width &&\n committedRef.current.height === next.height\n ) {\n return;\n }\n\n committedRef.current = next;\n setSize(next);\n\n const nextBreakpoint = getBreakpointInfo(next.width);\n setBreakpoint(prev =>\n breakpointEqual(prev, nextBreakpoint) ? prev : nextBreakpoint,\n );\n }, []);\n\n // Manual invocation only (`autoInvoke: false`) — reuses useDebounce's\n // debounced-callback machinery instead of duplicating it here.\n const debouncedCommit = useDebounce(commit, { delay, autoInvoke: false });\n\n // Measure synchronously before paint so the very first render reflects\n // the real size/breakpoint instead of the `{0,0}`/`xs` placeholder —\n // only later updates (from the observer/listener below) go through the\n // debounced path.\n useLayoutEffect(() => {\n const measured = viewport\n ? measureViewport()\n : (() => {\n const target = container ?? element ?? document.body;\n return target ? measureTarget(target) : null;\n })();\n\n if (!measured) {\n return;\n }\n\n latestRef.current = measured;\n commit();\n }, [container, element, viewport, commit]);\n\n useEffect(() => {\n if (viewport) {\n const onResize = () => {\n latestRef.current = measureViewport();\n debouncedCommit();\n };\n\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }\n\n const target = container ?? element ?? document.body;\n\n if (!target) {\n return;\n }\n\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n\n observerRef.current = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n latestRef.current = measureTarget(target);\n debouncedCommit();\n });\n });\n\n observerRef.current.observe(target);\n\n return () => {\n observerRef.current?.disconnect();\n observerRef.current = null;\n };\n }, [container, element, viewport, debouncedCommit]);\n\n return {\n size,\n breakpoint,\n ref,\n };\n};\n\nexport default useResponsiveSize;\n"],"mappings":"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"}
@@ -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"}