@jbpark/use-hooks 2.8.0 → 2.9.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.
@@ -2,11 +2,13 @@
2
2
  interface Options {
3
3
  delay?: number;
4
4
  autoInvoke?: boolean;
5
+ leading?: boolean;
5
6
  }
6
- declare const useDebounce: <T extends (...args: unknown[]) => unknown>(callback: T, {
7
+ declare const useDebounce: (callback: () => unknown, {
7
8
  delay,
8
- autoInvoke
9
- }: Options, deps?: React.DependencyList) => T;
9
+ autoInvoke,
10
+ leading
11
+ }: Options, deps?: React.DependencyList) => () => void;
10
12
  //#endregion
11
13
  export { useDebounce };
12
14
  //# sourceMappingURL=use-debounce.d.mts.map
@@ -1,27 +1,27 @@
1
1
  import { useEffect, useRef } from "react";
2
2
 
3
3
  //#region src/hooks/use-debounce.ts
4
- const useDebounce = (callback, { delay = 100, autoInvoke = true }, deps = []) => {
4
+ const useDebounce = (callback, { delay = 100, autoInvoke = true, leading = true }, deps = []) => {
5
5
  const timeoutRef = useRef(null);
6
6
  const callbackRef = useRef(callback);
7
+ const delayRef = useRef(delay);
7
8
  const prevDeps = useRef(void 0);
8
9
  useEffect(() => {
9
10
  callbackRef.current = callback;
11
+ delayRef.current = delay;
10
12
  });
11
13
  const stableDebouncedCallback = useRef(null);
12
- if (!stableDebouncedCallback.current) stableDebouncedCallback.current = ((...args) => {
14
+ if (!stableDebouncedCallback.current) stableDebouncedCallback.current = () => {
13
15
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
14
16
  timeoutRef.current = setTimeout(() => {
15
- callbackRef.current(...args);
16
- }, delay);
17
- });
17
+ callbackRef.current();
18
+ }, delayRef.current);
19
+ };
18
20
  useEffect(() => {
19
21
  const depsChanged = prevDeps.current === void 0 || prevDeps.current.length !== deps.length || prevDeps.current.some((dep, i) => dep !== deps[i]);
20
22
  if (depsChanged && timeoutRef.current) clearTimeout(timeoutRef.current);
21
- if (autoInvoke && depsChanged) {
22
- if (prevDeps.current === void 0) callbackRef.current();
23
- else if (stableDebouncedCallback.current) stableDebouncedCallback.current();
24
- }
23
+ if (autoInvoke && depsChanged) if (prevDeps.current === void 0 && leading) callbackRef.current();
24
+ else stableDebouncedCallback.current?.();
25
25
  prevDeps.current = deps;
26
26
  });
27
27
  useEffect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-debounce.mjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface Options {\n delay?: number;\n autoInvoke?: boolean;\n}\n\nconst useDebounce = <T extends (...args: unknown[]) => unknown>(\n callback: T,\n { delay = 100, autoInvoke = true }: Options,\n deps: React.DependencyList = [],\n): T => {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n const prevDeps = useRef<React.DependencyList | undefined>(undefined);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n const stableDebouncedCallback = useRef<T | null>(null);\n\n if (!stableDebouncedCallback.current) {\n stableDebouncedCallback.current = ((...args: Parameters<T>) => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n }, delay);\n }) as T;\n }\n\n useEffect(() => {\n const depsChanged =\n prevDeps.current === undefined ||\n prevDeps.current.length !== deps.length ||\n prevDeps.current.some((dep, i) => dep !== deps[i]);\n\n if (depsChanged && timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n if (autoInvoke && depsChanged) {\n const isFirstRender = prevDeps.current === undefined;\n if (isFirstRender) {\n callbackRef.current();\n } else if (stableDebouncedCallback.current) {\n stableDebouncedCallback.current();\n }\n }\n\n prevDeps.current = deps;\n });\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return stableDebouncedCallback.current;\n};\n\nexport default useDebounce;\n"],"mappings":";;;AAOA,MAAM,eACJ,UACA,EAAE,QAAQ,KAAK,aAAa,QAC5B,OAA6B,EAAE,KACzB;CACN,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CACpC,MAAM,WAAW,OAAyC,OAAU;AAEpE,iBAAgB;AACd,cAAY,UAAU;GACtB;CAEF,MAAM,0BAA0B,OAAiB,KAAK;AAEtD,KAAI,CAAC,wBAAwB,QAC3B,yBAAwB,YAAY,GAAG,SAAwB;AAC7D,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAGlC,aAAW,UAAU,iBAAiB;AACpC,eAAY,QAAQ,GAAG,KAAK;KAC3B,MAAM;;AAIb,iBAAgB;EACd,MAAM,cACJ,SAAS,YAAY,UACrB,SAAS,QAAQ,WAAW,KAAK,UACjC,SAAS,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG;AAEpD,MAAI,eAAe,WAAW,QAC5B,cAAa,WAAW,QAAQ;AAGlC,MAAI,cAAc,aAEhB;OADsB,SAAS,YAAY,OAEzC,aAAY,SAAS;YACZ,wBAAwB,QACjC,yBAAwB,SAAS;;AAIrC,WAAS,UAAU;GACnB;AAEF,iBAAgB;AACd,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;AAEN,QAAO,wBAAwB"}
1
+ {"version":3,"file":"use-debounce.mjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface Options {\n delay?: number;\n autoInvoke?: boolean;\n leading?: boolean;\n}\n\n// Auto-invokes `callback` (debounced by `delay`ms) whenever `deps` change,\n// and also returns a stable debounced version of `callback` for manual use.\n// `callback` intentionally takes no arguments — the deps-triggered\n// auto-invoke has no natural argument to supply, so an arg-taking callback\n// here was a latent type hole (it was always invoked with zero args\n// regardless of what the callback's own signature claimed).\nconst useDebounce = (\n callback: () => unknown,\n { delay = 100, autoInvoke = true, leading = true }: Options,\n deps: React.DependencyList = [],\n) => {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n // Read the latest `delay` when the timeout actually fires, instead of\n // baking in whatever `delay` was on the render that created the stable\n // debounced function (which otherwise never updates again).\n const delayRef = useRef(delay);\n const prevDeps = useRef<React.DependencyList | undefined>(undefined);\n\n useEffect(() => {\n callbackRef.current = callback;\n delayRef.current = delay;\n });\n\n const stableDebouncedCallback = useRef<(() => void) | null>(null);\n\n if (!stableDebouncedCallback.current) {\n stableDebouncedCallback.current = () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n callbackRef.current();\n }, delayRef.current);\n };\n }\n\n useEffect(() => {\n const depsChanged =\n prevDeps.current === undefined ||\n prevDeps.current.length !== deps.length ||\n prevDeps.current.some((dep, i) => dep !== deps[i]);\n\n if (depsChanged && timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n if (autoInvoke && depsChanged) {\n const isFirstRender = prevDeps.current === undefined;\n\n // `leading` makes the \"first invocation fires immediately, without\n // waiting `delay`ms\" behavior explicit and opt-out-able, instead of\n // an unconditional special case baked into the first render.\n if (isFirstRender && leading) {\n callbackRef.current();\n } else {\n stableDebouncedCallback.current?.();\n }\n }\n\n prevDeps.current = deps;\n });\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return stableDebouncedCallback.current;\n};\n\nexport default useDebounce;\n"],"mappings":";;;AAcA,MAAM,eACJ,UACA,EAAE,QAAQ,KAAK,aAAa,MAAM,UAAU,QAC5C,OAA6B,EAAE,KAC5B;CACH,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CAIpC,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,WAAW,OAAyC,OAAU;AAEpE,iBAAgB;AACd,cAAY,UAAU;AACtB,WAAS,UAAU;GACnB;CAEF,MAAM,0BAA0B,OAA4B,KAAK;AAEjE,KAAI,CAAC,wBAAwB,QAC3B,yBAAwB,gBAAgB;AACtC,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAGlC,aAAW,UAAU,iBAAiB;AACpC,eAAY,SAAS;KACpB,SAAS,QAAQ;;AAIxB,iBAAgB;EACd,MAAM,cACJ,SAAS,YAAY,UACrB,SAAS,QAAQ,WAAW,KAAK,UACjC,SAAS,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG;AAEpD,MAAI,eAAe,WAAW,QAC5B,cAAa,WAAW,QAAQ;AAGlC,MAAI,cAAc,YAMhB,KALsB,SAAS,YAAY,UAKtB,QACnB,aAAY,SAAS;MAErB,yBAAwB,WAAW;AAIvC,WAAS,UAAU;GACnB;AAEF,iBAAgB;AACd,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;AAEN,QAAO,wBAAwB"}
@@ -1,50 +1,79 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
1
+ import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
2
2
 
3
3
  //#region src/hooks/use-local-storage.ts
4
+ const cache = /* @__PURE__ */ new Map();
5
+ const listeners = /* @__PURE__ */ new Map();
6
+ function getListeners(key) {
7
+ let set = listeners.get(key);
8
+ if (!set) {
9
+ set = /* @__PURE__ */ new Set();
10
+ listeners.set(key, set);
11
+ }
12
+ return set;
13
+ }
14
+ function notify(key) {
15
+ getListeners(key).forEach((callback) => callback());
16
+ }
17
+ function getSnapshot(key, initialValue) {
18
+ const raw = typeof window === "undefined" ? null : localStorage.getItem(key);
19
+ const cached = cache.get(key);
20
+ if (cached && cached.raw === raw) return cached.value;
21
+ let value;
22
+ try {
23
+ value = raw === null ? initialValue : JSON.parse(raw);
24
+ } catch {
25
+ value = initialValue;
26
+ }
27
+ cache.set(key, {
28
+ raw,
29
+ value
30
+ });
31
+ return value;
32
+ }
33
+ function subscribe(key, callback) {
34
+ const set = getListeners(key);
35
+ set.add(callback);
36
+ const onStorage = (event) => {
37
+ if (event.key === key) callback();
38
+ };
39
+ if (typeof window !== "undefined") window.addEventListener("storage", onStorage);
40
+ return () => {
41
+ set.delete(callback);
42
+ if (typeof window !== "undefined") window.removeEventListener("storage", onStorage);
43
+ };
44
+ }
4
45
  const useLocalStorage = (key, initialValue) => {
5
46
  const initialValueRef = useRef(initialValue);
6
47
  useEffect(() => {
7
48
  initialValueRef.current = initialValue;
8
49
  });
9
- const [storedValue, setStoredValue] = useState(() => {
10
- if (typeof window === "undefined") return initialValue;
11
- try {
12
- const item = window.localStorage.getItem(key);
13
- return item ? JSON.parse(item) : initialValue;
14
- } catch {
15
- return initialValue;
16
- }
17
- });
18
50
  useEffect(() => {
19
51
  if (typeof window === "undefined") return;
20
52
  try {
21
- const item = window.localStorage.getItem(key);
22
- if (item) setStoredValue(JSON.parse(item));
23
- else window.localStorage.setItem(key, JSON.stringify(initialValueRef.current));
53
+ if (localStorage.getItem(key) === null) {
54
+ const raw = JSON.stringify(initialValueRef.current);
55
+ localStorage.setItem(key, raw);
56
+ cache.set(key, {
57
+ raw,
58
+ value: initialValueRef.current
59
+ });
60
+ notify(key);
61
+ }
24
62
  } catch (e) {
25
- console.error(`Error reading localStorage key "${key}":`, e);
63
+ console.error(`Error seeding localStorage key "${key}":`, e);
26
64
  }
27
65
  }, [key]);
28
- useEffect(() => {
29
- if (typeof window === "undefined") return;
30
- const onStorage = (event) => {
31
- if (event.key !== key) return;
32
- try {
33
- setStoredValue(event.newValue ? JSON.parse(event.newValue) : initialValueRef.current);
34
- } catch (e) {
35
- console.error(`Error parsing localStorage key "${key}" from storage event:`, e);
36
- }
37
- };
38
- window.addEventListener("storage", onStorage);
39
- return () => window.removeEventListener("storage", onStorage);
40
- }, [key]);
41
- return [storedValue, useCallback((value) => {
66
+ return [useSyncExternalStore(useCallback((callback) => subscribe(key, callback), [key]), useCallback(() => getSnapshot(key, initialValueRef.current), [key]), useCallback(() => initialValueRef.current, [])), useCallback((value) => {
42
67
  try {
43
- setStoredValue((prev) => {
44
- const valueToStore = value instanceof Function ? value(prev) : value;
45
- localStorage.setItem(key, JSON.stringify(valueToStore));
46
- return valueToStore;
68
+ const prev = getSnapshot(key, initialValueRef.current);
69
+ const valueToStore = value instanceof Function ? value(prev) : value;
70
+ const raw = JSON.stringify(valueToStore);
71
+ localStorage.setItem(key, raw);
72
+ cache.set(key, {
73
+ raw,
74
+ value: valueToStore
47
75
  });
76
+ notify(key);
48
77
  } catch (e) {
49
78
  console.error(`Error setting localStorage key "${key}":`, e);
50
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-local-storage.mjs","names":[],"sources":["../../src/hooks/use-local-storage.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nconst useLocalStorage = <T>(key: string, initialValue: T) => {\n const initialValueRef = useRef(initialValue);\n\n useEffect(() => {\n initialValueRef.current = initialValue;\n });\n\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (typeof window === 'undefined') {\n return initialValue;\n }\n try {\n const item = window.localStorage.getItem(key);\n return item ? (JSON.parse(item) as T) : initialValue;\n } catch {\n return initialValue;\n }\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const item = window.localStorage.getItem(key);\n if (item) {\n setStoredValue(JSON.parse(item) as T);\n } else {\n window.localStorage.setItem(\n key,\n JSON.stringify(initialValueRef.current),\n );\n }\n } catch (e) {\n console.error(`Error reading localStorage key \"${key}\":`, e);\n }\n }, [key]);\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const onStorage = (event: StorageEvent) => {\n if (event.key !== key) {\n return;\n }\n\n try {\n setStoredValue(\n event.newValue\n ? (JSON.parse(event.newValue) as T)\n : initialValueRef.current,\n );\n } catch (e) {\n console.error(\n `Error parsing localStorage key \"${key}\" from storage event:`,\n e,\n );\n }\n };\n\n window.addEventListener('storage', onStorage);\n return () => window.removeEventListener('storage', onStorage);\n }, [key]);\n\n const setValue = useCallback(\n (value: T | ((val: T) => T)) => {\n try {\n setStoredValue(prev => {\n const valueToStore = value instanceof Function ? value(prev) : value;\n localStorage.setItem(key, JSON.stringify(valueToStore));\n return valueToStore;\n });\n } catch (e) {\n console.error(`Error setting localStorage key \"${key}\":`, e);\n }\n },\n [key],\n );\n\n return [storedValue, setValue] as const;\n};\n\nexport default useLocalStorage;\n"],"mappings":";;;AAEA,MAAM,mBAAsB,KAAa,iBAAoB;CAC3D,MAAM,kBAAkB,OAAO,aAAa;AAE5C,iBAAgB;AACd,kBAAgB,UAAU;GAC1B;CAEF,MAAM,CAAC,aAAa,kBAAkB,eAAkB;AACtD,MAAI,OAAO,WAAW,YACpB,QAAO;AAET,MAAI;GACF,MAAM,OAAO,OAAO,aAAa,QAAQ,IAAI;AAC7C,UAAO,OAAQ,KAAK,MAAM,KAAK,GAAS;UAClC;AACN,UAAO;;GAET;AAEF,iBAAgB;AACd,MAAI,OAAO,WAAW,YACpB;AAGF,MAAI;GACF,MAAM,OAAO,OAAO,aAAa,QAAQ,IAAI;AAC7C,OAAI,KACF,gBAAe,KAAK,MAAM,KAAK,CAAM;OAErC,QAAO,aAAa,QAClB,KACA,KAAK,UAAU,gBAAgB,QAAQ,CACxC;WAEI,GAAG;AACV,WAAQ,MAAM,mCAAmC,IAAI,KAAK,EAAE;;IAE7D,CAAC,IAAI,CAAC;AAET,iBAAgB;AACd,MAAI,OAAO,WAAW,YACpB;EAGF,MAAM,aAAa,UAAwB;AACzC,OAAI,MAAM,QAAQ,IAChB;AAGF,OAAI;AACF,mBACE,MAAM,WACD,KAAK,MAAM,MAAM,SAAS,GAC3B,gBAAgB,QACrB;YACM,GAAG;AACV,YAAQ,MACN,mCAAmC,IAAI,wBACvC,EACD;;;AAIL,SAAO,iBAAiB,WAAW,UAAU;AAC7C,eAAa,OAAO,oBAAoB,WAAW,UAAU;IAC5D,CAAC,IAAI,CAAC;AAiBT,QAAO,CAAC,aAfS,aACd,UAA+B;AAC9B,MAAI;AACF,mBAAe,SAAQ;IACrB,MAAM,eAAe,iBAAiB,WAAW,MAAM,KAAK,GAAG;AAC/D,iBAAa,QAAQ,KAAK,KAAK,UAAU,aAAa,CAAC;AACvD,WAAO;KACP;WACK,GAAG;AACV,WAAQ,MAAM,mCAAmC,IAAI,KAAK,EAAE;;IAGhE,CAAC,IAAI,CACN,CAE6B"}
1
+ {"version":3,"file":"use-local-storage.mjs","names":[],"sources":["../../src/hooks/use-local-storage.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';\n\n// Module-level so every `useLocalStorage(key)` instance for the same key —\n// even across unrelated components in the same tab — shares one cache and\n// subscriber list. That's what makes same-tab sync possible: the native\n// `storage` event only fires in *other* tabs, so same-tab updates have to\n// be broadcast by us via `notify()`.\ninterface CacheEntry<T> {\n raw: string | null;\n value: T;\n}\n\nconst cache = new Map<string, CacheEntry<unknown>>();\nconst listeners = new Map<string, Set<() => void>>();\n\nfunction getListeners(key: string) {\n let set = listeners.get(key);\n if (!set) {\n set = new Set();\n listeners.set(key, set);\n }\n return set;\n}\n\nfunction notify(key: string) {\n getListeners(key).forEach(callback => callback());\n}\n\n// Returns the same cached value reference as long as the raw localStorage\n// string hasn't changed, so `useSyncExternalStore` doesn't see a \"new\"\n// snapshot (and re-render) on every call — only when the value actually\n// changed.\nfunction getSnapshot<T>(key: string, initialValue: T): T {\n const raw = typeof window === 'undefined' ? null : localStorage.getItem(key);\n const cached = cache.get(key) as CacheEntry<T> | undefined;\n\n if (cached && cached.raw === raw) {\n return cached.value;\n }\n\n let value: T;\n try {\n value = raw === null ? initialValue : (JSON.parse(raw) as T);\n } catch {\n value = initialValue;\n }\n\n cache.set(key, { raw, value });\n return value;\n}\n\nfunction subscribe(key: string, callback: () => void) {\n const set = getListeners(key);\n set.add(callback);\n\n const onStorage = (event: StorageEvent) => {\n if (event.key === key) {\n callback();\n }\n };\n\n if (typeof window !== 'undefined') {\n window.addEventListener('storage', onStorage);\n }\n\n return () => {\n set.delete(callback);\n if (typeof window !== 'undefined') {\n window.removeEventListener('storage', onStorage);\n }\n };\n}\n\nconst useLocalStorage = <T>(key: string, initialValue: T) => {\n const initialValueRef = useRef(initialValue);\n\n useEffect(() => {\n initialValueRef.current = initialValue;\n });\n\n // Seed the key with the initial value if it isn't set yet — done in an\n // effect (not inside the state updater below) so it's a one-shot side\n // effect rather than something React could invoke twice under\n // StrictMode/concurrent rendering.\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n if (localStorage.getItem(key) === null) {\n const raw = JSON.stringify(initialValueRef.current);\n localStorage.setItem(key, raw);\n cache.set(key, { raw, value: initialValueRef.current });\n notify(key);\n }\n } catch (e) {\n console.error(`Error seeding localStorage key \"${key}\":`, e);\n }\n }, [key]);\n\n const subscribeForKey = useCallback(\n (callback: () => void) => subscribe(key, callback),\n [key],\n );\n\n const getSnapshotForKey = useCallback(\n () => getSnapshot(key, initialValueRef.current),\n [key],\n );\n\n const getServerSnapshot = useCallback(() => initialValueRef.current, []);\n\n const storedValue = useSyncExternalStore(\n subscribeForKey,\n getSnapshotForKey,\n getServerSnapshot,\n );\n\n const setValue = useCallback(\n (value: T | ((val: T) => T)) => {\n try {\n const prev = getSnapshot(key, initialValueRef.current);\n const valueToStore = value instanceof Function ? value(prev) : value;\n const raw = JSON.stringify(valueToStore);\n\n localStorage.setItem(key, raw);\n cache.set(key, { raw, value: valueToStore });\n notify(key);\n } catch (e) {\n console.error(`Error setting localStorage key \"${key}\":`, e);\n }\n },\n [key],\n );\n\n return [storedValue, setValue] as const;\n};\n\nexport default useLocalStorage;\n"],"mappings":";;;AAYA,MAAM,wBAAQ,IAAI,KAAkC;AACpD,MAAM,4BAAY,IAAI,KAA8B;AAEpD,SAAS,aAAa,KAAa;CACjC,IAAI,MAAM,UAAU,IAAI,IAAI;AAC5B,KAAI,CAAC,KAAK;AACR,wBAAM,IAAI,KAAK;AACf,YAAU,IAAI,KAAK,IAAI;;AAEzB,QAAO;;AAGT,SAAS,OAAO,KAAa;AAC3B,cAAa,IAAI,CAAC,SAAQ,aAAY,UAAU,CAAC;;AAOnD,SAAS,YAAe,KAAa,cAAoB;CACvD,MAAM,MAAM,OAAO,WAAW,cAAc,OAAO,aAAa,QAAQ,IAAI;CAC5E,MAAM,SAAS,MAAM,IAAI,IAAI;AAE7B,KAAI,UAAU,OAAO,QAAQ,IAC3B,QAAO,OAAO;CAGhB,IAAI;AACJ,KAAI;AACF,UAAQ,QAAQ,OAAO,eAAgB,KAAK,MAAM,IAAI;SAChD;AACN,UAAQ;;AAGV,OAAM,IAAI,KAAK;EAAE;EAAK;EAAO,CAAC;AAC9B,QAAO;;AAGT,SAAS,UAAU,KAAa,UAAsB;CACpD,MAAM,MAAM,aAAa,IAAI;AAC7B,KAAI,IAAI,SAAS;CAEjB,MAAM,aAAa,UAAwB;AACzC,MAAI,MAAM,QAAQ,IAChB,WAAU;;AAId,KAAI,OAAO,WAAW,YACpB,QAAO,iBAAiB,WAAW,UAAU;AAG/C,cAAa;AACX,MAAI,OAAO,SAAS;AACpB,MAAI,OAAO,WAAW,YACpB,QAAO,oBAAoB,WAAW,UAAU;;;AAKtD,MAAM,mBAAsB,KAAa,iBAAoB;CAC3D,MAAM,kBAAkB,OAAO,aAAa;AAE5C,iBAAgB;AACd,kBAAgB,UAAU;GAC1B;AAMF,iBAAgB;AACd,MAAI,OAAO,WAAW,YACpB;AAGF,MAAI;AACF,OAAI,aAAa,QAAQ,IAAI,KAAK,MAAM;IACtC,MAAM,MAAM,KAAK,UAAU,gBAAgB,QAAQ;AACnD,iBAAa,QAAQ,KAAK,IAAI;AAC9B,UAAM,IAAI,KAAK;KAAE;KAAK,OAAO,gBAAgB;KAAS,CAAC;AACvD,WAAO,IAAI;;WAEN,GAAG;AACV,WAAQ,MAAM,mCAAmC,IAAI,KAAK,EAAE;;IAE7D,CAAC,IAAI,CAAC;AAqCT,QAAO,CAvBa,qBAZI,aACrB,aAAyB,UAAU,KAAK,SAAS,EAClD,CAAC,IAAI,CACN,EAEyB,kBAClB,YAAY,KAAK,gBAAgB,QAAQ,EAC/C,CAAC,IAAI,CACN,EAEyB,kBAAkB,gBAAgB,SAAS,EAAE,CAAC,CAMvE,EAEgB,aACd,UAA+B;AAC9B,MAAI;GACF,MAAM,OAAO,YAAY,KAAK,gBAAgB,QAAQ;GACtD,MAAM,eAAe,iBAAiB,WAAW,MAAM,KAAK,GAAG;GAC/D,MAAM,MAAM,KAAK,UAAU,aAAa;AAExC,gBAAa,QAAQ,KAAK,IAAI;AAC9B,SAAM,IAAI,KAAK;IAAE;IAAK,OAAO;IAAc,CAAC;AAC5C,UAAO,IAAI;WACJ,GAAG;AACV,WAAQ,MAAM,mCAAmC,IAAI,KAAK,EAAE;;IAGhE,CAAC,IAAI,CACN,CAE6B"}
@@ -1,5 +1,8 @@
1
1
  //#region src/hooks/use-recursive-timeout.d.ts
2
- declare const useRecursiveTimeout: <T>(callback: () => Promise<T> | (() => void), delay: number | null) => void;
2
+ interface Options {
3
+ stopOnError?: boolean;
4
+ }
5
+ declare const useRecursiveTimeout: <T>(callback: () => Promise<T> | void, delay: number | null, options?: Options) => void;
3
6
  //#endregion
4
7
  export { useRecursiveTimeout };
5
8
  //# sourceMappingURL=use-recursive-timeout.d.mts.map
@@ -1,26 +1,33 @@
1
1
  import { useEffect, useRef } from "react";
2
2
 
3
3
  //#region src/hooks/use-recursive-timeout.ts
4
- const useRecursiveTimeout = (callback, delay) => {
4
+ const useRecursiveTimeout = (callback, delay, options = {}) => {
5
5
  const savedCallback = useRef(callback);
6
+ const savedStopOnError = useRef(options.stopOnError ?? false);
6
7
  useEffect(() => {
7
8
  savedCallback.current = callback;
8
- }, [callback]);
9
+ savedStopOnError.current = options.stopOnError ?? false;
10
+ });
9
11
  useEffect(() => {
12
+ if (delay === null) return;
10
13
  let id;
14
+ let cancelled = false;
15
+ const scheduleNext = () => {
16
+ if (!cancelled) id = setTimeout(tick, delay);
17
+ };
11
18
  function tick() {
12
19
  const ret = savedCallback.current();
13
- if (ret instanceof Promise) ret.then(() => {
14
- if (delay) id = setTimeout(tick, delay);
20
+ if (ret instanceof Promise) ret.then(scheduleNext, (error) => {
21
+ console.error("useRecursiveTimeout callback rejected:", error);
22
+ if (!savedStopOnError.current) scheduleNext();
15
23
  });
16
- else if (delay) id = setTimeout(tick, delay);
17
- }
18
- if (delay) {
19
- id = setTimeout(tick, delay);
20
- return () => {
21
- clearTimeout(id);
22
- };
24
+ else scheduleNext();
23
25
  }
26
+ id = setTimeout(tick, delay);
27
+ return () => {
28
+ cancelled = true;
29
+ clearTimeout(id);
30
+ };
24
31
  }, [delay]);
25
32
  };
26
33
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-recursive-timeout.mjs","names":[],"sources":["../../src/hooks/use-recursive-timeout.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nconst useRecursiveTimeout = <T>(\n callback: () => Promise<T> | (() => void),\n delay: number | null,\n) => {\n const savedCallback = useRef(callback);\n\n useEffect(() => {\n savedCallback.current = callback;\n }, [callback]);\n\n useEffect(() => {\n let id: ReturnType<typeof setTimeout>;\n\n function tick() {\n const ret = savedCallback.current();\n\n if (ret instanceof Promise) {\n ret.then(() => {\n if (delay) {\n id = setTimeout(tick, delay);\n }\n });\n } else {\n if (delay) {\n id = setTimeout(tick, delay);\n }\n }\n }\n\n if (delay) {\n id = setTimeout(tick, delay);\n return () => {\n clearTimeout(id);\n };\n }\n }, [delay]);\n};\n\nexport default useRecursiveTimeout;\n"],"mappings":";;;AAEA,MAAM,uBACJ,UACA,UACG;CACH,MAAM,gBAAgB,OAAO,SAAS;AAEtC,iBAAgB;AACd,gBAAc,UAAU;IACvB,CAAC,SAAS,CAAC;AAEd,iBAAgB;EACd,IAAI;EAEJ,SAAS,OAAO;GACd,MAAM,MAAM,cAAc,SAAS;AAEnC,OAAI,eAAe,QACjB,KAAI,WAAW;AACb,QAAI,MACF,MAAK,WAAW,MAAM,MAAM;KAE9B;YAEE,MACF,MAAK,WAAW,MAAM,MAAM;;AAKlC,MAAI,OAAO;AACT,QAAK,WAAW,MAAM,MAAM;AAC5B,gBAAa;AACX,iBAAa,GAAG;;;IAGnB,CAAC,MAAM,CAAC"}
1
+ {"version":3,"file":"use-recursive-timeout.mjs","names":[],"sources":["../../src/hooks/use-recursive-timeout.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface Options {\n // Whether a rejected callback should stop the loop instead of scheduling\n // the next tick. Defaults to false — one failed tick shouldn't kill a\n // polling loop, but the rejection is still logged either way.\n stopOnError?: boolean;\n}\n\nconst useRecursiveTimeout = <T>(\n callback: () => Promise<T> | void,\n delay: number | null,\n options: Options = {},\n) => {\n const savedCallback = useRef(callback);\n const savedStopOnError = useRef(options.stopOnError ?? false);\n\n useEffect(() => {\n savedCallback.current = callback;\n savedStopOnError.current = options.stopOnError ?? false;\n });\n\n useEffect(() => {\n if (delay === null) {\n return;\n }\n\n let id: ReturnType<typeof setTimeout>;\n let cancelled = false;\n\n const scheduleNext = () => {\n if (!cancelled) {\n id = setTimeout(tick, delay);\n }\n };\n\n function tick() {\n const ret = savedCallback.current();\n\n if (ret instanceof Promise) {\n ret.then(scheduleNext, (error: unknown) => {\n console.error('useRecursiveTimeout callback rejected:', error);\n if (!savedStopOnError.current) {\n scheduleNext();\n }\n });\n } else {\n scheduleNext();\n }\n }\n\n id = setTimeout(tick, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(id);\n };\n }, [delay]);\n};\n\nexport default useRecursiveTimeout;\n"],"mappings":";;;AASA,MAAM,uBACJ,UACA,OACA,UAAmB,EAAE,KAClB;CACH,MAAM,gBAAgB,OAAO,SAAS;CACtC,MAAM,mBAAmB,OAAO,QAAQ,eAAe,MAAM;AAE7D,iBAAgB;AACd,gBAAc,UAAU;AACxB,mBAAiB,UAAU,QAAQ,eAAe;GAClD;AAEF,iBAAgB;AACd,MAAI,UAAU,KACZ;EAGF,IAAI;EACJ,IAAI,YAAY;EAEhB,MAAM,qBAAqB;AACzB,OAAI,CAAC,UACH,MAAK,WAAW,MAAM,MAAM;;EAIhC,SAAS,OAAO;GACd,MAAM,MAAM,cAAc,SAAS;AAEnC,OAAI,eAAe,QACjB,KAAI,KAAK,eAAe,UAAmB;AACzC,YAAQ,MAAM,0CAA0C,MAAM;AAC9D,QAAI,CAAC,iBAAiB,QACpB,eAAc;KAEhB;OAEF,eAAc;;AAIlB,OAAK,WAAW,MAAM,MAAM;AAE5B,eAAa;AACX,eAAY;AACZ,gBAAa,GAAG;;IAEjB,CAAC,MAAM,CAAC"}
@@ -1,5 +1,5 @@
1
1
  //#region src/hooks/use-viewport.d.ts
2
- type ViewportInfo = VisualViewport | {
2
+ interface ViewportInfo {
3
3
  width: number;
4
4
  height: number;
5
5
  offsetLeft: number;
@@ -7,7 +7,7 @@ type ViewportInfo = VisualViewport | {
7
7
  pageLeft: number;
8
8
  pageTop: number;
9
9
  scale: number;
10
- };
10
+ }
11
11
  interface Options {
12
12
  isInApp?: boolean;
13
13
  debounce?: number;
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
 
3
3
  //#region src/hooks/use-viewport.ts
4
+ const viewportEqual = (a, b) => a.width === b.width && a.height === b.height && a.offsetLeft === b.offsetLeft && a.offsetTop === b.offsetTop && a.pageLeft === b.pageLeft && a.pageTop === b.pageTop && a.scale === b.scale;
4
5
  const useViewport = (options = {}) => {
5
6
  const { isInApp = false, debounce = 100 } = options;
6
7
  const [viewport, setViewport] = useState({
@@ -21,7 +22,6 @@ const useViewport = (options = {}) => {
21
22
  return Math.max(windowHeight, documentHeight, bodyHeight);
22
23
  }, []);
23
24
  const readViewport = useCallback(() => {
24
- if (window.visualViewport && !isInApp) return window.visualViewport;
25
25
  return {
26
26
  width: window.visualViewport?.width || window.innerWidth,
27
27
  height: isInApp ? getAppViewHeight() : window.visualViewport?.height || window.innerHeight,
@@ -34,13 +34,16 @@ const useViewport = (options = {}) => {
34
34
  }, [isInApp, getAppViewHeight]);
35
35
  useEffect(() => {
36
36
  let timeoutId;
37
+ const commitViewport = (next) => {
38
+ setViewport((prev) => viewportEqual(prev, next) ? prev : next);
39
+ };
37
40
  const debouncedUpdate = () => {
38
41
  clearTimeout(timeoutId);
39
42
  timeoutId = setTimeout(() => {
40
- setViewport(readViewport());
43
+ commitViewport(readViewport());
41
44
  }, debounce);
42
45
  };
43
- const immediateUpdate = () => setViewport(readViewport());
46
+ const immediateUpdate = () => commitViewport(readViewport());
44
47
  immediateUpdate();
45
48
  const events = ["resize", "orientationchange"];
46
49
  if (isInApp) events.push("focus", "blur", "touchstart", "touchend");
@@ -1 +1 @@
1
- {"version":3,"file":"use-viewport.mjs","names":[],"sources":["../../src/hooks/use-viewport.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\n\ntype ViewportInfo =\n | VisualViewport\n | {\n width: number;\n height: number;\n offsetLeft: number;\n offsetTop: number;\n pageLeft: number;\n pageTop: number;\n scale: number;\n };\n\ninterface Options {\n isInApp?: boolean;\n debounce?: number;\n}\n\nconst useViewport = (options: Options = {}) => {\n const { isInApp = false, debounce = 100 } = options;\n\n const [viewport, setViewport] = useState<ViewportInfo>({\n width: 0,\n height: 0,\n offsetLeft: 0,\n offsetTop: 0,\n pageLeft: 0,\n pageTop: 0,\n scale: 1,\n });\n\n const getAppViewHeight = useCallback(() => {\n const windowHeight = window.innerHeight;\n const visualHeight = window.visualViewport?.height || windowHeight;\n const documentHeight = document.documentElement.clientHeight;\n const bodyHeight = document.body.clientHeight;\n\n if (window.visualViewport && Math.abs(visualHeight - windowHeight) > 100) {\n return visualHeight;\n }\n\n return Math.max(windowHeight, documentHeight, bodyHeight);\n }, []);\n\n const readViewport = useCallback((): ViewportInfo => {\n if (window.visualViewport && !isInApp) {\n return window.visualViewport;\n }\n\n const width = window.visualViewport?.width || window.innerWidth;\n const height = isInApp\n ? getAppViewHeight()\n : window.visualViewport?.height || window.innerHeight;\n\n return {\n width,\n height,\n offsetLeft: window.visualViewport?.offsetLeft || 0,\n offsetTop: window.visualViewport?.offsetTop || 0,\n pageLeft: window.scrollX ?? window.pageXOffset ?? 0,\n pageTop: window.scrollY ?? window.pageYOffset ?? 0,\n scale: window.visualViewport?.scale || 1,\n };\n }, [isInApp, getAppViewHeight]);\n\n useEffect(() => {\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const debouncedUpdate = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n setViewport(readViewport());\n }, debounce);\n };\n\n const immediateUpdate = () => setViewport(readViewport());\n\n immediateUpdate();\n\n const events = ['resize', 'orientationchange'];\n\n if (isInApp) {\n events.push('focus', 'blur', 'touchstart', 'touchend');\n }\n\n events.forEach(event => {\n if (event === 'resize' || event === 'orientationchange') {\n window.addEventListener(event, debouncedUpdate);\n } else {\n window.addEventListener(event, immediateUpdate, { passive: true });\n }\n });\n\n if (window.visualViewport) {\n window.visualViewport.addEventListener('resize', immediateUpdate);\n window.visualViewport.addEventListener('scroll', immediateUpdate);\n }\n\n let intervalId: ReturnType<typeof setInterval>;\n if (isInApp) {\n let lastHeight = readViewport().height;\n intervalId = setInterval(() => {\n const currentHeight = readViewport().height;\n if (Math.abs(currentHeight - lastHeight) > 50) {\n lastHeight = currentHeight;\n immediateUpdate();\n }\n }, 500);\n }\n\n return () => {\n clearTimeout(timeoutId);\n if (intervalId) clearInterval(intervalId);\n\n events.forEach(event => {\n window.removeEventListener(\n event,\n event === 'resize' || event === 'orientationchange'\n ? debouncedUpdate\n : immediateUpdate,\n );\n });\n\n if (window.visualViewport) {\n window.visualViewport.removeEventListener('resize', immediateUpdate);\n window.visualViewport.removeEventListener('scroll', immediateUpdate);\n }\n };\n }, [readViewport, isInApp, debounce]);\n\n return viewport;\n};\n\nexport default useViewport;\n"],"mappings":";;;AAmBA,MAAM,eAAe,UAAmB,EAAE,KAAK;CAC7C,MAAM,EAAE,UAAU,OAAO,WAAW,QAAQ;CAE5C,MAAM,CAAC,UAAU,eAAe,SAAuB;EACrD,OAAO;EACP,QAAQ;EACR,YAAY;EACZ,WAAW;EACX,UAAU;EACV,SAAS;EACT,OAAO;EACR,CAAC;CAEF,MAAM,mBAAmB,kBAAkB;EACzC,MAAM,eAAe,OAAO;EAC5B,MAAM,eAAe,OAAO,gBAAgB,UAAU;EACtD,MAAM,iBAAiB,SAAS,gBAAgB;EAChD,MAAM,aAAa,SAAS,KAAK;AAEjC,MAAI,OAAO,kBAAkB,KAAK,IAAI,eAAe,aAAa,GAAG,IACnE,QAAO;AAGT,SAAO,KAAK,IAAI,cAAc,gBAAgB,WAAW;IACxD,EAAE,CAAC;CAEN,MAAM,eAAe,kBAAgC;AACnD,MAAI,OAAO,kBAAkB,CAAC,QAC5B,QAAO,OAAO;AAQhB,SAAO;GACL,OANY,OAAO,gBAAgB,SAAS,OAAO;GAOnD,QANa,UACX,kBAAkB,GAClB,OAAO,gBAAgB,UAAU,OAAO;GAK1C,YAAY,OAAO,gBAAgB,cAAc;GACjD,WAAW,OAAO,gBAAgB,aAAa;GAC/C,UAAU,OAAO,WAAW,OAAO,eAAe;GAClD,SAAS,OAAO,WAAW,OAAO,eAAe;GACjD,OAAO,OAAO,gBAAgB,SAAS;GACxC;IACA,CAAC,SAAS,iBAAiB,CAAC;AAE/B,iBAAgB;EACd,IAAI;EAEJ,MAAM,wBAAwB;AAC5B,gBAAa,UAAU;AACvB,eAAY,iBAAiB;AAC3B,gBAAY,cAAc,CAAC;MAC1B,SAAS;;EAGd,MAAM,wBAAwB,YAAY,cAAc,CAAC;AAEzD,mBAAiB;EAEjB,MAAM,SAAS,CAAC,UAAU,oBAAoB;AAE9C,MAAI,QACF,QAAO,KAAK,SAAS,QAAQ,cAAc,WAAW;AAGxD,SAAO,SAAQ,UAAS;AACtB,OAAI,UAAU,YAAY,UAAU,oBAClC,QAAO,iBAAiB,OAAO,gBAAgB;OAE/C,QAAO,iBAAiB,OAAO,iBAAiB,EAAE,SAAS,MAAM,CAAC;IAEpE;AAEF,MAAI,OAAO,gBAAgB;AACzB,UAAO,eAAe,iBAAiB,UAAU,gBAAgB;AACjE,UAAO,eAAe,iBAAiB,UAAU,gBAAgB;;EAGnE,IAAI;AACJ,MAAI,SAAS;GACX,IAAI,aAAa,cAAc,CAAC;AAChC,gBAAa,kBAAkB;IAC7B,MAAM,gBAAgB,cAAc,CAAC;AACrC,QAAI,KAAK,IAAI,gBAAgB,WAAW,GAAG,IAAI;AAC7C,kBAAa;AACb,sBAAiB;;MAElB,IAAI;;AAGT,eAAa;AACX,gBAAa,UAAU;AACvB,OAAI,WAAY,eAAc,WAAW;AAEzC,UAAO,SAAQ,UAAS;AACtB,WAAO,oBACL,OACA,UAAU,YAAY,UAAU,sBAC5B,kBACA,gBACL;KACD;AAEF,OAAI,OAAO,gBAAgB;AACzB,WAAO,eAAe,oBAAoB,UAAU,gBAAgB;AACpE,WAAO,eAAe,oBAAoB,UAAU,gBAAgB;;;IAGvE;EAAC;EAAc;EAAS;EAAS,CAAC;AAErC,QAAO"}
1
+ {"version":3,"file":"use-viewport.mjs","names":[],"sources":["../../src/hooks/use-viewport.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\n\ninterface ViewportInfo {\n width: number;\n height: number;\n offsetLeft: number;\n offsetTop: number;\n pageLeft: number;\n pageTop: number;\n scale: number;\n}\n\nconst viewportEqual = (a: ViewportInfo, b: ViewportInfo) =>\n a.width === b.width &&\n a.height === b.height &&\n a.offsetLeft === b.offsetLeft &&\n a.offsetTop === b.offsetTop &&\n a.pageLeft === b.pageLeft &&\n a.pageTop === b.pageTop &&\n a.scale === b.scale;\n\ninterface Options {\n isInApp?: boolean;\n debounce?: number;\n}\n\nconst useViewport = (options: Options = {}) => {\n const { isInApp = false, debounce = 100 } = options;\n\n const [viewport, setViewport] = useState<ViewportInfo>({\n width: 0,\n height: 0,\n offsetLeft: 0,\n offsetTop: 0,\n pageLeft: 0,\n pageTop: 0,\n scale: 1,\n });\n\n const getAppViewHeight = useCallback(() => {\n const windowHeight = window.innerHeight;\n const visualHeight = window.visualViewport?.height || windowHeight;\n const documentHeight = document.documentElement.clientHeight;\n const bodyHeight = document.body.clientHeight;\n\n if (window.visualViewport && Math.abs(visualHeight - windowHeight) > 100) {\n return visualHeight;\n }\n\n return Math.max(windowHeight, documentHeight, bodyHeight);\n }, []);\n\n // Always builds a fresh plain-object snapshot rather than returning\n // `window.visualViewport` itself — that instance is a single mutable\n // object the browser updates in place, so returning it directly meant\n // every read produced the exact same reference and `setViewport` never\n // saw a change (no re-render, ever).\n const readViewport = useCallback((): ViewportInfo => {\n const width = window.visualViewport?.width || window.innerWidth;\n const height = isInApp\n ? getAppViewHeight()\n : window.visualViewport?.height || window.innerHeight;\n\n return {\n width,\n height,\n offsetLeft: window.visualViewport?.offsetLeft || 0,\n offsetTop: window.visualViewport?.offsetTop || 0,\n pageLeft: window.scrollX ?? window.pageXOffset ?? 0,\n pageTop: window.scrollY ?? window.pageYOffset ?? 0,\n scale: window.visualViewport?.scale || 1,\n };\n }, [isInApp, getAppViewHeight]);\n\n useEffect(() => {\n let timeoutId: ReturnType<typeof setTimeout>;\n\n // Snapshots are freshly-allocated objects on every read, so gate the\n // state update on an actual value change instead of always replacing\n // the reference — otherwise every resize/scroll tick would re-render\n // consumers even when nothing visibly moved.\n const commitViewport = (next: ViewportInfo) => {\n setViewport(prev => (viewportEqual(prev, next) ? prev : next));\n };\n\n const debouncedUpdate = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n commitViewport(readViewport());\n }, debounce);\n };\n\n const immediateUpdate = () => commitViewport(readViewport());\n\n immediateUpdate();\n\n const events = ['resize', 'orientationchange'];\n\n if (isInApp) {\n events.push('focus', 'blur', 'touchstart', 'touchend');\n }\n\n events.forEach(event => {\n if (event === 'resize' || event === 'orientationchange') {\n window.addEventListener(event, debouncedUpdate);\n } else {\n window.addEventListener(event, immediateUpdate, { passive: true });\n }\n });\n\n if (window.visualViewport) {\n window.visualViewport.addEventListener('resize', immediateUpdate);\n window.visualViewport.addEventListener('scroll', immediateUpdate);\n }\n\n let intervalId: ReturnType<typeof setInterval>;\n if (isInApp) {\n let lastHeight = readViewport().height;\n intervalId = setInterval(() => {\n const currentHeight = readViewport().height;\n if (Math.abs(currentHeight - lastHeight) > 50) {\n lastHeight = currentHeight;\n immediateUpdate();\n }\n }, 500);\n }\n\n return () => {\n clearTimeout(timeoutId);\n if (intervalId) clearInterval(intervalId);\n\n events.forEach(event => {\n window.removeEventListener(\n event,\n event === 'resize' || event === 'orientationchange'\n ? debouncedUpdate\n : immediateUpdate,\n );\n });\n\n if (window.visualViewport) {\n window.visualViewport.removeEventListener('resize', immediateUpdate);\n window.visualViewport.removeEventListener('scroll', immediateUpdate);\n }\n };\n }, [readViewport, isInApp, debounce]);\n\n return viewport;\n};\n\nexport default useViewport;\n"],"mappings":";;;AAYA,MAAM,iBAAiB,GAAiB,MACtC,EAAE,UAAU,EAAE,SACd,EAAE,WAAW,EAAE,UACf,EAAE,eAAe,EAAE,cACnB,EAAE,cAAc,EAAE,aAClB,EAAE,aAAa,EAAE,YACjB,EAAE,YAAY,EAAE,WAChB,EAAE,UAAU,EAAE;AAOhB,MAAM,eAAe,UAAmB,EAAE,KAAK;CAC7C,MAAM,EAAE,UAAU,OAAO,WAAW,QAAQ;CAE5C,MAAM,CAAC,UAAU,eAAe,SAAuB;EACrD,OAAO;EACP,QAAQ;EACR,YAAY;EACZ,WAAW;EACX,UAAU;EACV,SAAS;EACT,OAAO;EACR,CAAC;CAEF,MAAM,mBAAmB,kBAAkB;EACzC,MAAM,eAAe,OAAO;EAC5B,MAAM,eAAe,OAAO,gBAAgB,UAAU;EACtD,MAAM,iBAAiB,SAAS,gBAAgB;EAChD,MAAM,aAAa,SAAS,KAAK;AAEjC,MAAI,OAAO,kBAAkB,KAAK,IAAI,eAAe,aAAa,GAAG,IACnE,QAAO;AAGT,SAAO,KAAK,IAAI,cAAc,gBAAgB,WAAW;IACxD,EAAE,CAAC;CAON,MAAM,eAAe,kBAAgC;AAMnD,SAAO;GACL,OANY,OAAO,gBAAgB,SAAS,OAAO;GAOnD,QANa,UACX,kBAAkB,GAClB,OAAO,gBAAgB,UAAU,OAAO;GAK1C,YAAY,OAAO,gBAAgB,cAAc;GACjD,WAAW,OAAO,gBAAgB,aAAa;GAC/C,UAAU,OAAO,WAAW,OAAO,eAAe;GAClD,SAAS,OAAO,WAAW,OAAO,eAAe;GACjD,OAAO,OAAO,gBAAgB,SAAS;GACxC;IACA,CAAC,SAAS,iBAAiB,CAAC;AAE/B,iBAAgB;EACd,IAAI;EAMJ,MAAM,kBAAkB,SAAuB;AAC7C,gBAAY,SAAS,cAAc,MAAM,KAAK,GAAG,OAAO,KAAM;;EAGhE,MAAM,wBAAwB;AAC5B,gBAAa,UAAU;AACvB,eAAY,iBAAiB;AAC3B,mBAAe,cAAc,CAAC;MAC7B,SAAS;;EAGd,MAAM,wBAAwB,eAAe,cAAc,CAAC;AAE5D,mBAAiB;EAEjB,MAAM,SAAS,CAAC,UAAU,oBAAoB;AAE9C,MAAI,QACF,QAAO,KAAK,SAAS,QAAQ,cAAc,WAAW;AAGxD,SAAO,SAAQ,UAAS;AACtB,OAAI,UAAU,YAAY,UAAU,oBAClC,QAAO,iBAAiB,OAAO,gBAAgB;OAE/C,QAAO,iBAAiB,OAAO,iBAAiB,EAAE,SAAS,MAAM,CAAC;IAEpE;AAEF,MAAI,OAAO,gBAAgB;AACzB,UAAO,eAAe,iBAAiB,UAAU,gBAAgB;AACjE,UAAO,eAAe,iBAAiB,UAAU,gBAAgB;;EAGnE,IAAI;AACJ,MAAI,SAAS;GACX,IAAI,aAAa,cAAc,CAAC;AAChC,gBAAa,kBAAkB;IAC7B,MAAM,gBAAgB,cAAc,CAAC;AACrC,QAAI,KAAK,IAAI,gBAAgB,WAAW,GAAG,IAAI;AAC7C,kBAAa;AACb,sBAAiB;;MAElB,IAAI;;AAGT,eAAa;AACX,gBAAa,UAAU;AACvB,OAAI,WAAY,eAAc,WAAW;AAEzC,UAAO,SAAQ,UAAS;AACtB,WAAO,oBACL,OACA,UAAU,YAAY,UAAU,sBAC5B,kBACA,gBACL;KACD;AAEF,OAAI,OAAO,gBAAgB;AACzB,WAAO,eAAe,oBAAoB,UAAU,gBAAgB;AACpE,WAAO,eAAe,oBAAoB,UAAU,gBAAgB;;;IAGvE;EAAC;EAAc;EAAS;EAAS,CAAC;AAErC,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbpark/use-hooks",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "A collection of reusable React 19 hooks for common UI and interaction patterns",
5
5
  "keywords": [
6
6
  "react",