@develemit/web-hooks 0.1.0 → 0.3.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 (101) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +311 -0
  3. package/dist/auto-grow.cjs +60 -0
  4. package/dist/auto-grow.d.cts +14 -0
  5. package/dist/auto-grow.d.ts +14 -0
  6. package/dist/auto-grow.js +9 -0
  7. package/dist/chunk-4PVVTQ5V.js +74 -0
  8. package/dist/chunk-5CA5RIMH.js +34 -0
  9. package/dist/chunk-5KIH2XLV.js +136 -0
  10. package/dist/chunk-5XRVPZHD.js +125 -0
  11. package/dist/chunk-6KCRVSLS.js +80 -0
  12. package/dist/chunk-DNU57XEJ.js +82 -0
  13. package/dist/chunk-DQBKU7HI.js +46 -0
  14. package/dist/chunk-EXEJI4FK.js +43 -0
  15. package/dist/chunk-FE3BQD2I.js +44 -0
  16. package/dist/chunk-HTVNN2NM.js +29 -0
  17. package/dist/chunk-IWQDAJEP.js +69 -0
  18. package/dist/chunk-JG3GPTMY.js +101 -0
  19. package/dist/chunk-LZKAGDER.js +22 -0
  20. package/dist/chunk-NKLEB5TR.js +98 -0
  21. package/dist/chunk-S4YKMGQE.js +23 -0
  22. package/dist/chunk-TNEJ34T5.js +86 -0
  23. package/dist/chunk-UJ366T3Y.js +146 -0
  24. package/dist/chunk-UPVNSGIK.js +72 -0
  25. package/dist/chunk-WVM2LTWQ.js +42 -0
  26. package/dist/chunk-WXL22EWK.js +21 -0
  27. package/dist/chunk-YFMMSLMB.js +41 -0
  28. package/dist/chunk-Z3X27PCA.js +108 -0
  29. package/dist/clipboard.cjs +18 -0
  30. package/dist/clipboard.d.cts +10 -1
  31. package/dist/clipboard.d.ts +10 -1
  32. package/dist/clipboard.js +8 -57
  33. package/dist/debounce.cjs +96 -0
  34. package/dist/debounce.d.cts +21 -0
  35. package/dist/debounce.d.ts +21 -0
  36. package/dist/debounce.js +11 -0
  37. package/dist/drawer-swipe.cjs +16 -4
  38. package/dist/drawer-swipe.d.cts +7 -3
  39. package/dist/drawer-swipe.d.ts +7 -3
  40. package/dist/drawer-swipe.js +16 -101
  41. package/dist/escape.cjs +1 -0
  42. package/dist/escape.js +3 -17
  43. package/dist/focus-trap.cjs +3 -1
  44. package/dist/focus-trap.js +6 -72
  45. package/dist/hotkey.cjs +127 -0
  46. package/dist/hotkey.d.cts +31 -0
  47. package/dist/hotkey.d.ts +31 -0
  48. package/dist/hotkey.js +15 -0
  49. package/dist/index.cjs +1207 -7
  50. package/dist/index.d.cts +20 -60
  51. package/dist/index.d.ts +20 -60
  52. package/dist/index.js +167 -247
  53. package/dist/keyboard-inset.cjs +113 -0
  54. package/dist/keyboard-inset.d.cts +25 -0
  55. package/dist/keyboard-inset.d.ts +25 -0
  56. package/dist/keyboard-inset.js +11 -0
  57. package/dist/media-query.cjs +90 -0
  58. package/dist/media-query.d.cts +12 -0
  59. package/dist/media-query.d.ts +12 -0
  60. package/dist/media-query.js +17 -0
  61. package/dist/online-status.cjs +46 -0
  62. package/dist/online-status.d.cts +3 -0
  63. package/dist/online-status.d.ts +3 -0
  64. package/dist/online-status.js +7 -0
  65. package/dist/outside-press.cjs +73 -0
  66. package/dist/outside-press.d.cts +17 -0
  67. package/dist/outside-press.d.ts +17 -0
  68. package/dist/outside-press.js +11 -0
  69. package/dist/push.cjs +201 -0
  70. package/dist/push.d.cts +37 -0
  71. package/dist/push.d.ts +37 -0
  72. package/dist/push.js +14 -0
  73. package/dist/pwa.cjs +198 -0
  74. package/dist/pwa.d.cts +35 -0
  75. package/dist/pwa.d.ts +35 -0
  76. package/dist/pwa.js +22 -0
  77. package/dist/scroll-lock.cjs +107 -0
  78. package/dist/scroll-lock.d.cts +8 -0
  79. package/dist/scroll-lock.d.ts +8 -0
  80. package/dist/scroll-lock.js +11 -0
  81. package/dist/service-worker.cjs +128 -0
  82. package/dist/service-worker.d.cts +23 -0
  83. package/dist/service-worker.d.ts +23 -0
  84. package/dist/service-worker.js +11 -0
  85. package/dist/share.cjs +139 -0
  86. package/dist/share.d.cts +23 -0
  87. package/dist/share.d.ts +23 -0
  88. package/dist/share.js +16 -0
  89. package/dist/storage-state.cjs +161 -0
  90. package/dist/storage-state.d.cts +14 -0
  91. package/dist/storage-state.d.ts +14 -0
  92. package/dist/storage-state.js +7 -0
  93. package/dist/unsaved-guard.cjs +48 -0
  94. package/dist/unsaved-guard.d.cts +5 -0
  95. package/dist/unsaved-guard.d.ts +5 -0
  96. package/dist/unsaved-guard.js +9 -0
  97. package/dist/visibility-polling.cjs +98 -0
  98. package/dist/visibility-polling.d.cts +14 -0
  99. package/dist/visibility-polling.d.ts +14 -0
  100. package/dist/visibility-polling.js +9 -0
  101. package/package.json +85 -2
package/dist/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -22,21 +23,73 @@ var src_exports = {};
22
23
  __export(src_exports, {
23
24
  BACK_ZONE_PX: () => BACK_ZONE_PX,
24
25
  CLOSE_DISTANCE_PX: () => CLOSE_DISTANCE_PX,
26
+ COARSE_POINTER_QUERY: () => COARSE_POINTER_QUERY,
27
+ DEFAULT_IGNORE_SELECTOR: () => DEFAULT_IGNORE_SELECTOR,
25
28
  FOCUSABLE_SELECTOR: () => FOCUSABLE_SELECTOR,
26
29
  FORWARD_ZONE_PX: () => FORWARD_ZONE_PX,
27
30
  MAX_SWIPE_DURATION_MS: () => MAX_SWIPE_DURATION_MS,
28
31
  OPEN_DISTANCE_PX: () => OPEN_DISTANCE_PX,
32
+ REDUCED_MOTION_QUERY: () => REDUCED_MOTION_QUERY,
29
33
  VERTICAL_CANCEL_PX: () => VERTICAL_CANCEL_PX,
34
+ canShareFiles: () => canShareFiles,
35
+ computeKeyboardInset: () => computeKeyboardInset,
30
36
  copyText: () => copyText,
37
+ createDebouncer: () => createDebouncer,
38
+ deriveUpdateState: () => deriveUpdateState,
39
+ detectApple: () => detectApple,
40
+ detectPlatform: () => detectPlatform,
31
41
  focusableWithin: () => focusableWithin,
42
+ getPushSupport: () => getPushSupport,
43
+ handleBeforeUnload: () => handleBeforeUnload,
44
+ isEditableElement: () => isEditableElement,
45
+ isEditableTarget: () => isEditableTarget,
46
+ isInIgnoredRegion: () => isInIgnoredRegion,
32
47
  isInOpenZone: () => isInOpenZone,
33
48
  isInsideHorizontalScroller: () => isInsideHorizontalScroller,
49
+ isIos: () => isIos,
50
+ isIosSafari: () => isIosSafari,
51
+ isOutside: () => isOutside,
52
+ isPrimaryPress: () => isPrimaryPress,
53
+ isShareCancel: () => isShareCancel,
54
+ isStandalone: () => isStandalone,
55
+ lockScroll: () => lockScroll,
56
+ matchHotkey: () => matchHotkey,
57
+ nextDelay: () => nextDelay,
34
58
  nextFocusTarget: () => nextFocusTarget,
59
+ nextHeight: () => nextHeight,
60
+ parseHotkey: () => parseHotkey,
61
+ readText: () => readText,
35
62
  resolveSwipeIntent: () => resolveSwipeIntent,
63
+ shareOrCopy: () => shareOrCopy,
64
+ shareTextFor: () => shareTextFor,
65
+ shouldResubscribe: () => shouldResubscribe,
66
+ startInstallPromptCapture: () => startInstallPromptCapture,
67
+ unlockScroll: () => unlockScroll,
68
+ unregisterAll: () => unregisterAll,
69
+ urlBase64ToUint8Array: () => urlBase64ToUint8Array,
70
+ useAutoGrowTextarea: () => useAutoGrowTextarea,
71
+ useCoarsePointer: () => useCoarsePointer,
36
72
  useCopyFeedback: () => useCopyFeedback,
73
+ useDebouncedCallback: () => useDebouncedCallback,
74
+ useDebouncedValue: () => useDebouncedValue,
37
75
  useDrawerSwipe: () => useDrawerSwipe,
38
76
  useEscape: () => useEscape,
39
- useFocusTrap: () => useFocusTrap
77
+ useFocusTrap: () => useFocusTrap,
78
+ useHotkey: () => useHotkey,
79
+ useInstallPrompt: () => useInstallPrompt,
80
+ useIsStandalone: () => useIsStandalone,
81
+ useKeyboardInset: () => useKeyboardInset,
82
+ useMediaQuery: () => useMediaQuery,
83
+ useOnlineStatus: () => useOnlineStatus,
84
+ useOutsidePress: () => useOutsidePress,
85
+ usePushSubscription: () => usePushSubscription,
86
+ useReducedMotion: () => useReducedMotion,
87
+ useScrollLock: () => useScrollLock,
88
+ useServiceWorker: () => useServiceWorker,
89
+ useShareFeedback: () => useShareFeedback,
90
+ useStorageState: () => useStorageState,
91
+ useUnsavedChangesGuard: () => useUnsavedChangesGuard,
92
+ useVisibilityPolling: () => useVisibilityPolling
40
93
  });
41
94
  module.exports = __toCommonJS(src_exports);
42
95
 
@@ -51,7 +104,8 @@ var HORIZONTAL_DOMINANCE = 1.5;
51
104
  function isInOpenZone(x, viewportWidth) {
52
105
  return x > BACK_ZONE_PX && x < viewportWidth - FORWARD_ZONE_PX;
53
106
  }
54
- function resolveSwipeIntent(start, end, drawerOpen, viewportWidth) {
107
+ function resolveSwipeIntent(start, end, drawerOpen, viewportWidth, startedInIgnoredRegion = false) {
108
+ if (startedInIgnoredRegion) return null;
55
109
  const dx = end.x - start.x;
56
110
  const dy = end.y - start.y;
57
111
  if (end.time - start.time > MAX_SWIPE_DURATION_MS) return null;
@@ -73,6 +127,11 @@ function isInsideHorizontalScroller(target) {
73
127
  }
74
128
  return false;
75
129
  }
130
+ var DEFAULT_IGNORE_SELECTOR = "[data-no-swipe]";
131
+ function isInIgnoredRegion(target, selector) {
132
+ if (!selector || !(target instanceof Element)) return false;
133
+ return target.closest(selector) !== null;
134
+ }
76
135
 
77
136
  // src/drawer-swipe/use-drawer-swipe.ts
78
137
  var import_react = require("react");
@@ -84,17 +143,19 @@ function useDrawerSwipe({
84
143
  onOpen,
85
144
  onClose,
86
145
  enabled = true,
87
- maxViewportWidth = 768
146
+ maxViewportWidth = 768,
147
+ ignoreSelector = DEFAULT_IGNORE_SELECTOR
88
148
  }) {
89
149
  const start = (0, import_react.useRef)(null);
90
- const handlers = (0, import_react.useRef)({ drawerOpen, onOpen, onClose, maxViewportWidth });
91
- handlers.current = { drawerOpen, onOpen, onClose, maxViewportWidth };
150
+ const handlers = (0, import_react.useRef)({ drawerOpen, onOpen, onClose, maxViewportWidth, ignoreSelector });
151
+ handlers.current = { drawerOpen, onOpen, onClose, maxViewportWidth, ignoreSelector };
92
152
  (0, import_react.useEffect)(() => {
93
153
  if (!enabled) return;
94
154
  function handleStart(event) {
95
155
  start.current = null;
96
156
  if (event.touches.length !== 1) return;
97
157
  if (isWideViewport(handlers.current.maxViewportWidth)) return;
158
+ if (isInIgnoredRegion(event.target, handlers.current.ignoreSelector)) return;
98
159
  if (!handlers.current.drawerOpen && isInsideHorizontalScroller(event.target)) {
99
160
  return;
100
161
  }
@@ -171,6 +232,9 @@ function useFocusTrap(containerRef, {
171
232
  if (active && !openerRef.current && typeof document !== "undefined") {
172
233
  openerRef.current = document.activeElement;
173
234
  }
235
+ (0, import_react2.useEffect)(() => {
236
+ if (!active) openerRef.current = null;
237
+ }, [active]);
174
238
  (0, import_react2.useEffect)(() => {
175
239
  const container = containerRef.current;
176
240
  if (!active || !container) return;
@@ -207,7 +271,6 @@ function useFocusTrap(containerRef, {
207
271
  document.removeEventListener("keydown", onKeyDown);
208
272
  activeTraps.splice(activeTraps.indexOf(token), 1);
209
273
  const opener = returnFocusRef?.current ?? openerRef.current;
210
- openerRef.current = null;
211
274
  if (restoreFocus && opener?.isConnected) opener.focus();
212
275
  };
213
276
  }, [active, containerRef, initialFocusRef, returnFocusRef, restoreFocus]);
@@ -223,6 +286,7 @@ function useEscape(onEscape, active = true) {
223
286
  (0, import_react3.useEffect)(() => {
224
287
  if (!active) return;
225
288
  function onKeyDown(event) {
289
+ if (event.isComposing || event.keyCode === 229) return;
226
290
  if (event.key === "Escape") handlerRef.current();
227
291
  }
228
292
  document.addEventListener("keydown", onKeyDown);
@@ -252,8 +316,10 @@ function execCommandCopy(text) {
252
316
  textarea.style.top = "-1000px";
253
317
  textarea.style.left = "-1000px";
254
318
  textarea.style.fontSize = "16px";
319
+ const previouslyFocused = document.activeElement;
255
320
  document.body.appendChild(textarea);
256
321
  try {
322
+ textarea.focus({ preventScroll: true });
257
323
  textarea.select();
258
324
  textarea.setSelectionRange(0, text.length);
259
325
  return document.execCommand("copy");
@@ -261,6 +327,9 @@ function execCommandCopy(text) {
261
327
  return false;
262
328
  } finally {
263
329
  document.body.removeChild(textarea);
330
+ if (previouslyFocused instanceof HTMLElement && previouslyFocused !== document.body) {
331
+ previouslyFocused.focus({ preventScroll: true });
332
+ }
264
333
  }
265
334
  }
266
335
 
@@ -286,23 +355,1154 @@ function useCopyFeedback({ resetMs = 1500 } = {}) {
286
355
  );
287
356
  return { copy, status };
288
357
  }
358
+
359
+ // src/clipboard/read-text.ts
360
+ async function readText() {
361
+ const hasClipboardApi = typeof navigator !== "undefined" && !!navigator.clipboard?.readText && typeof window !== "undefined" && window.isSecureContext;
362
+ if (!hasClipboardApi) return { ok: false, reason: "unavailable" };
363
+ try {
364
+ return { ok: true, text: await navigator.clipboard.readText() };
365
+ } catch {
366
+ return { ok: false, reason: "denied" };
367
+ }
368
+ }
369
+
370
+ // src/scroll-lock/use-scroll-lock.ts
371
+ var import_react5 = require("react");
372
+
373
+ // src/scroll-lock/lock-styles.ts
374
+ var LOCKED_PROPERTIES = [
375
+ "position",
376
+ "top",
377
+ "left",
378
+ "right",
379
+ "width",
380
+ "overflow",
381
+ "paddingRight"
382
+ ];
383
+ function computeLockStyles({ scrollY, scrollbarWidth, existingPaddingRight }) {
384
+ const styles = {
385
+ position: "fixed",
386
+ top: `-${scrollY}px`,
387
+ left: "0",
388
+ right: "0",
389
+ width: "100%",
390
+ overflow: "hidden"
391
+ };
392
+ if (scrollbarWidth > 0) styles.paddingRight = `${existingPaddingRight + scrollbarWidth}px`;
393
+ return styles;
394
+ }
395
+
396
+ // src/scroll-lock/scroll-lock.ts
397
+ var count = 0;
398
+ var saved = null;
399
+ function applyFirstLock() {
400
+ const { body, documentElement } = document;
401
+ const inline = {};
402
+ for (const property of LOCKED_PROPERTIES) inline[property] = body.style[property];
403
+ saved = { scrollY: window.scrollY, inline };
404
+ const styles = computeLockStyles({
405
+ scrollY: saved.scrollY,
406
+ scrollbarWidth: Math.max(0, window.innerWidth - documentElement.clientWidth),
407
+ existingPaddingRight: parseFloat(getComputedStyle(body).paddingRight) || 0
408
+ });
409
+ Object.assign(body.style, styles);
410
+ }
411
+ function restoreLastLock() {
412
+ if (!saved) return;
413
+ const { scrollY, inline } = saved;
414
+ saved = null;
415
+ Object.assign(document.body.style, inline);
416
+ window.scrollTo(0, scrollY);
417
+ }
418
+ function lockScroll() {
419
+ if (count === 0) applyFirstLock();
420
+ count += 1;
421
+ let released = false;
422
+ return () => {
423
+ if (released) return;
424
+ released = true;
425
+ count -= 1;
426
+ if (count === 0) restoreLastLock();
427
+ };
428
+ }
429
+ function unlockScroll() {
430
+ if (count === 0) return;
431
+ count -= 1;
432
+ if (count === 0) restoreLastLock();
433
+ }
434
+
435
+ // src/scroll-lock/use-scroll-lock.ts
436
+ function useScrollLock(active = true) {
437
+ (0, import_react5.useEffect)(() => {
438
+ if (!active) return;
439
+ return lockScroll();
440
+ }, [active]);
441
+ }
442
+
443
+ // src/media-query/use-media-query.ts
444
+ var import_react6 = require("react");
445
+
446
+ // src/media-query/media-query.ts
447
+ var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
448
+ var COARSE_POINTER_QUERY = "(pointer: coarse)";
449
+ var lists = /* @__PURE__ */ new WeakMap();
450
+ function getList(query) {
451
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return null;
452
+ const owner = window.matchMedia;
453
+ let byQuery = lists.get(owner);
454
+ if (!byQuery) {
455
+ byQuery = /* @__PURE__ */ new Map();
456
+ lists.set(owner, byQuery);
457
+ }
458
+ let list = byQuery.get(query);
459
+ if (!list) {
460
+ list = owner.call(window, query);
461
+ byQuery.set(query, list);
462
+ }
463
+ return list;
464
+ }
465
+ function getMatches(query) {
466
+ return getList(query)?.matches ?? null;
467
+ }
468
+ function subscribe(query, onChange) {
469
+ const list = getList(query);
470
+ if (!list) return () => {
471
+ };
472
+ if (typeof list.addEventListener === "function") {
473
+ list.addEventListener("change", onChange);
474
+ return () => list.removeEventListener("change", onChange);
475
+ }
476
+ list.addListener(onChange);
477
+ return () => list.removeListener(onChange);
478
+ }
479
+
480
+ // src/media-query/use-media-query.ts
481
+ function useMediaQuery(query, options = {}) {
482
+ const serverDefault = options.serverDefault ?? false;
483
+ const subscribeToQuery = (0, import_react6.useCallback)((onChange) => subscribe(query, onChange), [query]);
484
+ const getSnapshot2 = (0, import_react6.useCallback)(() => getMatches(query) ?? serverDefault, [query, serverDefault]);
485
+ const getServerSnapshot2 = (0, import_react6.useCallback)(() => serverDefault, [serverDefault]);
486
+ return (0, import_react6.useSyncExternalStore)(subscribeToQuery, getSnapshot2, getServerSnapshot2);
487
+ }
488
+ function useReducedMotion(options) {
489
+ return useMediaQuery(REDUCED_MOTION_QUERY, options);
490
+ }
491
+ function useCoarsePointer(options) {
492
+ return useMediaQuery(COARSE_POINTER_QUERY, options);
493
+ }
494
+
495
+ // src/outside-press/outside-press.ts
496
+ function isOutside(target, elements, ignoreSelector) {
497
+ if (!target) return false;
498
+ if (!target.isConnected) return false;
499
+ if (ignoreSelector && isInIgnoredRegion2(target, ignoreSelector)) return false;
500
+ return !elements.some((el) => el?.contains(target));
501
+ }
502
+ function isPrimaryPress(event) {
503
+ return !(event.pointerType === "mouse" && event.button !== 0);
504
+ }
505
+ function isInIgnoredRegion2(target, selector) {
506
+ const element = target instanceof Element ? target : target.parentElement;
507
+ return element?.closest(selector) != null;
508
+ }
509
+
510
+ // src/outside-press/use-outside-press.ts
511
+ var import_react7 = require("react");
512
+ function useOutsidePress(refs, onOutsidePress, { enabled = true, ignore } = {}) {
513
+ const refsRef = (0, import_react7.useRef)(refs);
514
+ const handlerRef = (0, import_react7.useRef)(onOutsidePress);
515
+ const ignoreRef = (0, import_react7.useRef)(ignore);
516
+ (0, import_react7.useEffect)(() => {
517
+ ignoreRef.current = ignore;
518
+ refsRef.current = refs;
519
+ handlerRef.current = onOutsidePress;
520
+ });
521
+ (0, import_react7.useEffect)(() => {
522
+ if (!enabled) return;
523
+ function onPointerDown(event) {
524
+ if (!isPrimaryPress(event)) return;
525
+ const list = Array.isArray(refsRef.current) ? refsRef.current : [refsRef.current];
526
+ const elements = list.map((ref) => ref.current);
527
+ if (isOutside(event.target, elements, ignoreRef.current)) handlerRef.current(event);
528
+ }
529
+ document.addEventListener("pointerdown", onPointerDown, true);
530
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
531
+ }, [enabled]);
532
+ }
533
+
534
+ // src/visibility-polling/schedule.ts
535
+ function nextDelay(intervalMs, jitter, random) {
536
+ const fraction = Math.min(1, Math.max(0, jitter));
537
+ return intervalMs + random() * fraction * intervalMs;
538
+ }
539
+
540
+ // src/visibility-polling/use-visibility-polling.ts
541
+ var import_react8 = require("react");
542
+ var isHidden = () => document.visibilityState === "hidden";
543
+ function useVisibilityPolling(fn, intervalMs, { enabled = true, immediate = true, jitter = 0, runOnVisible = true } = {}) {
544
+ const fnRef = (0, import_react8.useRef)(fn);
545
+ const optionsRef = (0, import_react8.useRef)({ immediate, jitter, runOnVisible });
546
+ (0, import_react8.useEffect)(() => {
547
+ fnRef.current = fn;
548
+ optionsRef.current = { immediate, jitter, runOnVisible };
549
+ });
550
+ const runningRef = (0, import_react8.useRef)(false);
551
+ const scheduleRef = (0, import_react8.useRef)(null);
552
+ (0, import_react8.useEffect)(() => {
553
+ if (!enabled) return;
554
+ let active = true;
555
+ let timer = null;
556
+ function clearTimer() {
557
+ if (timer !== null) clearTimeout(timer);
558
+ timer = null;
559
+ }
560
+ function schedule() {
561
+ clearTimer();
562
+ if (!active || isHidden()) return;
563
+ timer = setTimeout(run, nextDelay(intervalMs, optionsRef.current.jitter, Math.random));
564
+ }
565
+ async function run() {
566
+ timer = null;
567
+ if (!active || runningRef.current || isHidden()) return;
568
+ runningRef.current = true;
569
+ try {
570
+ await fnRef.current();
571
+ } catch {
572
+ } finally {
573
+ runningRef.current = false;
574
+ }
575
+ scheduleRef.current?.();
576
+ }
577
+ function onVisibilityChange() {
578
+ if (isHidden()) {
579
+ clearTimer();
580
+ } else if (optionsRef.current.runOnVisible) {
581
+ clearTimer();
582
+ void run();
583
+ } else if (!runningRef.current) {
584
+ schedule();
585
+ }
586
+ }
587
+ scheduleRef.current = schedule;
588
+ if (optionsRef.current.immediate && !isHidden()) void run();
589
+ else schedule();
590
+ document.addEventListener("visibilitychange", onVisibilityChange);
591
+ return () => {
592
+ active = false;
593
+ scheduleRef.current = null;
594
+ clearTimer();
595
+ document.removeEventListener("visibilitychange", onVisibilityChange);
596
+ };
597
+ }, [enabled, intervalMs]);
598
+ }
599
+
600
+ // src/debounce/debouncer.ts
601
+ function createDebouncer(getFn, getMs) {
602
+ let timer;
603
+ let pending;
604
+ function clear() {
605
+ if (timer !== void 0) clearTimeout(timer);
606
+ timer = void 0;
607
+ pending = void 0;
608
+ }
609
+ function run() {
610
+ const args = pending;
611
+ clear();
612
+ if (args) getFn()(...args);
613
+ }
614
+ return {
615
+ call(...args) {
616
+ if (timer !== void 0) clearTimeout(timer);
617
+ pending = args;
618
+ timer = setTimeout(run, getMs());
619
+ },
620
+ cancel: clear,
621
+ flush() {
622
+ if (pending) run();
623
+ },
624
+ isPending: () => pending !== void 0
625
+ };
626
+ }
627
+
628
+ // src/debounce/use-debounced-value.ts
629
+ var import_react9 = require("react");
630
+ function useDebouncedValue(value, ms) {
631
+ const [debounced, setDebounced] = (0, import_react9.useState)(value);
632
+ (0, import_react9.useEffect)(() => {
633
+ const timer = setTimeout(() => setDebounced(value), ms);
634
+ return () => clearTimeout(timer);
635
+ }, [value, ms]);
636
+ return debounced;
637
+ }
638
+
639
+ // src/debounce/use-debounced-callback.ts
640
+ var import_react10 = require("react");
641
+ function useDebouncedCallback(fn, ms) {
642
+ const fnRef = (0, import_react10.useRef)(fn);
643
+ const msRef = (0, import_react10.useRef)(ms);
644
+ (0, import_react10.useEffect)(() => {
645
+ fnRef.current = fn;
646
+ msRef.current = ms;
647
+ });
648
+ const [debounced] = (0, import_react10.useState)(() => {
649
+ const debouncer = createDebouncer(
650
+ () => fnRef.current,
651
+ () => msRef.current
652
+ );
653
+ return Object.assign((...args) => debouncer.call(...args), {
654
+ cancel: debouncer.cancel,
655
+ flush: debouncer.flush
656
+ });
657
+ });
658
+ (0, import_react10.useEffect)(() => () => debounced.cancel(), [debounced]);
659
+ return debounced;
660
+ }
661
+
662
+ // src/storage-state/use-storage-state.ts
663
+ var import_react11 = require("react");
664
+
665
+ // src/storage-state/storage-store.ts
666
+ function getStorage(kind) {
667
+ try {
668
+ if (typeof window === "undefined") return null;
669
+ return kind === "session" ? window.sessionStorage : window.localStorage;
670
+ } catch {
671
+ return null;
672
+ }
673
+ }
674
+ function readRaw(storage, key) {
675
+ try {
676
+ return storage ? storage.getItem(key) : null;
677
+ } catch {
678
+ return null;
679
+ }
680
+ }
681
+ function writeRaw(storage, key, value) {
682
+ try {
683
+ if (!storage) return false;
684
+ storage.setItem(key, value);
685
+ return true;
686
+ } catch {
687
+ return false;
688
+ }
689
+ }
690
+ function removeRaw(storage, key) {
691
+ try {
692
+ if (!storage) return false;
693
+ storage.removeItem(key);
694
+ return true;
695
+ } catch {
696
+ return false;
697
+ }
698
+ }
699
+ function parseOr(raw, parse, fallback) {
700
+ if (raw === null) return fallback;
701
+ try {
702
+ return parse(raw);
703
+ } catch {
704
+ return fallback;
705
+ }
706
+ }
707
+ var slot = (kind, key) => `${kind}\0${key}`;
708
+ var memory = /* @__PURE__ */ new Map();
709
+ var listeners = /* @__PURE__ */ new Map();
710
+ function readCurrent(kind, key) {
711
+ const held = memory.get(slot(kind, key));
712
+ if (held !== void 0) return held;
713
+ return readRaw(getStorage(kind), key);
714
+ }
715
+ function notify(id) {
716
+ for (const listener of [...listeners.get(id) ?? []]) listener();
717
+ }
718
+ function writeCurrent(kind, key, raw) {
719
+ const id = slot(kind, key);
720
+ if (writeRaw(getStorage(kind), key, raw)) memory.delete(id);
721
+ else memory.set(id, raw);
722
+ notify(id);
723
+ }
724
+ function removeCurrent(kind, key) {
725
+ const id = slot(kind, key);
726
+ if (removeRaw(getStorage(kind), key)) memory.delete(id);
727
+ else memory.set(id, null);
728
+ notify(id);
729
+ }
730
+ function onStorageEvent(event) {
731
+ const kinds = [];
732
+ if (!event.storageArea || event.storageArea === getStorage("local")) kinds.push("local");
733
+ if (!event.storageArea || event.storageArea === getStorage("session")) kinds.push("session");
734
+ for (const id of [...listeners.keys()]) {
735
+ const [kind, key] = id.split("\0");
736
+ if (!kinds.includes(kind)) continue;
737
+ if (event.key !== null && event.key !== key) continue;
738
+ memory.delete(id);
739
+ notify(id);
740
+ }
741
+ }
742
+ function subscribeToKey(kind, key, listener) {
743
+ const id = slot(kind, key);
744
+ const wasEmpty = listeners.size === 0;
745
+ let set2 = listeners.get(id);
746
+ if (!set2) listeners.set(id, set2 = /* @__PURE__ */ new Set());
747
+ set2.add(listener);
748
+ if (wasEmpty) window.addEventListener("storage", onStorageEvent);
749
+ return () => {
750
+ const current = listeners.get(id);
751
+ current?.delete(listener);
752
+ if (current?.size === 0) listeners.delete(id);
753
+ if (listeners.size === 0) window.removeEventListener("storage", onStorageEvent);
754
+ };
755
+ }
756
+
757
+ // src/storage-state/use-storage-state.ts
758
+ function useStorageState(key, defaultValue, options = {}) {
759
+ const kind = options.storage ?? "local";
760
+ const serialize = options.serialize ?? JSON.stringify;
761
+ const parse = options.parse ?? JSON.parse;
762
+ const latest = (0, import_react11.useRef)({ serialize, parse, defaultValue });
763
+ (0, import_react11.useEffect)(() => {
764
+ latest.current = { serialize, parse, defaultValue };
765
+ });
766
+ const cache = (0, import_react11.useRef)(null);
767
+ const subscribe3 = (0, import_react11.useCallback)((onChange) => subscribeToKey(kind, key, onChange), [kind, key]);
768
+ const getSnapshot2 = (0, import_react11.useCallback)(() => {
769
+ const raw = readCurrent(kind, key);
770
+ const hit = cache.current;
771
+ if (hit && hit.kind === kind && hit.key === key && hit.raw === raw && hit.parse === parse) return hit.value;
772
+ const value2 = parseOr(raw, parse, defaultValue);
773
+ cache.current = { kind, key, raw, parse, value: value2 };
774
+ return value2;
775
+ }, [kind, key, parse, defaultValue]);
776
+ const getServerSnapshot2 = (0, import_react11.useCallback)(() => defaultValue, [defaultValue]);
777
+ const value = (0, import_react11.useSyncExternalStore)(subscribe3, getSnapshot2, getServerSnapshot2);
778
+ const set2 = (0, import_react11.useCallback)(
779
+ (next) => {
780
+ const { serialize: encode, parse: decode, defaultValue: fallback } = latest.current;
781
+ try {
782
+ const resolved = typeof next === "function" ? next(parseOr(readCurrent(kind, key), decode, fallback)) : next;
783
+ writeCurrent(kind, key, encode(resolved));
784
+ } catch {
785
+ }
786
+ },
787
+ [kind, key]
788
+ );
789
+ const remove = (0, import_react11.useCallback)(() => removeCurrent(kind, key), [kind, key]);
790
+ return [value, set2, remove];
791
+ }
792
+
793
+ // src/unsaved-guard/handle-before-unload.ts
794
+ function handleBeforeUnload(event) {
795
+ event.preventDefault();
796
+ event.returnValue = "";
797
+ }
798
+
799
+ // src/unsaved-guard/use-unsaved-changes-guard.ts
800
+ var import_react12 = require("react");
801
+ function useUnsavedChangesGuard(dirty) {
802
+ (0, import_react12.useEffect)(() => {
803
+ if (!dirty) return;
804
+ window.addEventListener("beforeunload", handleBeforeUnload);
805
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
806
+ }, [dirty]);
807
+ }
808
+
809
+ // src/hotkey/parse-hotkey.ts
810
+ var KEY_ALIASES = {
811
+ esc: "escape",
812
+ space: " ",
813
+ plus: "+",
814
+ up: "arrowup",
815
+ down: "arrowdown",
816
+ left: "arrowleft",
817
+ right: "arrowright"
818
+ };
819
+ function parseHotkey(combo) {
820
+ const parsed = { key: "", mod: false, ctrl: false, meta: false, alt: false, shift: false };
821
+ const parts = combo.trim().toLowerCase().split(/\+(?=.)/);
822
+ for (const part of parts) {
823
+ const token = part.trim();
824
+ if (token === "mod" || token === "ctrl" || token === "meta" || token === "alt" || token === "shift") {
825
+ parsed[token] = true;
826
+ } else {
827
+ parsed.key = KEY_ALIASES[token] ?? token;
828
+ }
829
+ }
830
+ if (!parsed.key) throw new Error(`Invalid hotkey "${combo}": no key`);
831
+ return parsed;
832
+ }
833
+
834
+ // src/hotkey/match-hotkey.ts
835
+ var CODE_FOR_SYMBOL = {
836
+ "/": "Slash",
837
+ ".": "Period",
838
+ ",": "Comma",
839
+ ";": "Semicolon",
840
+ "'": "Quote",
841
+ "[": "BracketLeft",
842
+ "]": "BracketRight",
843
+ "\\": "Backslash",
844
+ "-": "Minus",
845
+ "=": "Equal",
846
+ "`": "Backquote"
847
+ };
848
+ function physicalCode(key) {
849
+ if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
850
+ if (/^[0-9]$/.test(key)) return `Digit${key}`;
851
+ return CODE_FOR_SYMBOL[key] ?? null;
852
+ }
853
+ function keyMatches(event, parsed) {
854
+ if (event.key.toLowerCase() === parsed.key) return true;
855
+ return parsed.shift && physicalCode(parsed.key) === event.code;
856
+ }
857
+ function matchHotkey(event, combo, isApple) {
858
+ const parsed = typeof combo === "string" ? parseHotkey(combo) : combo;
859
+ const wantMeta = parsed.meta || parsed.mod && isApple;
860
+ const wantCtrl = parsed.ctrl || parsed.mod && !isApple;
861
+ return event.metaKey === wantMeta && event.ctrlKey === wantCtrl && event.altKey === parsed.alt && event.shiftKey === parsed.shift && keyMatches(event, parsed);
862
+ }
863
+ function detectApple(nav) {
864
+ if (!nav) return false;
865
+ const platform = nav.userAgentData?.platform || nav.platform || "";
866
+ return /mac|iphone|ipad|ipod/i.test(platform);
867
+ }
868
+ function isEditableTarget(target) {
869
+ const el = target;
870
+ if (!el || typeof el.tagName !== "string") return false;
871
+ const tag = el.tagName.toLowerCase();
872
+ return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable === true;
873
+ }
874
+
875
+ // src/hotkey/use-hotkey.ts
876
+ var import_react13 = require("react");
877
+ function useHotkey(combo, onHotkey, options = {}) {
878
+ const { enabled = true, allowInInputs = false, preventDefault = true } = options;
879
+ const handlerRef = (0, import_react13.useRef)(onHotkey);
880
+ (0, import_react13.useEffect)(() => {
881
+ handlerRef.current = onHotkey;
882
+ });
883
+ (0, import_react13.useEffect)(() => {
884
+ if (!enabled) return;
885
+ const parsed = parseHotkey(combo);
886
+ function onKeyDown(event) {
887
+ if (event.isComposing || event.keyCode === 229) return;
888
+ if (!allowInInputs && isEditableTarget(event.target)) return;
889
+ if (!matchHotkey(event, parsed, detectApple(navigator))) return;
890
+ if (preventDefault) event.preventDefault();
891
+ handlerRef.current(event);
892
+ }
893
+ document.addEventListener("keydown", onKeyDown);
894
+ return () => document.removeEventListener("keydown", onKeyDown);
895
+ }, [combo, enabled, allowInInputs, preventDefault]);
896
+ }
897
+
898
+ // src/auto-grow/next-height.ts
899
+ function nextHeight(scrollHeight, maxHeight) {
900
+ const content = Math.ceil(scrollHeight);
901
+ if (maxHeight !== void 0 && content > maxHeight) return { height: maxHeight, capped: true };
902
+ return { height: content, capped: false };
903
+ }
904
+
905
+ // src/auto-grow/use-auto-grow-textarea.ts
906
+ var import_react14 = require("react");
907
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react14.useEffect : import_react14.useLayoutEffect;
908
+ function useAutoGrowTextarea(ref, value, { maxHeight } = {}) {
909
+ const resize = (0, import_react14.useCallback)(() => {
910
+ const el = ref.current;
911
+ if (!el) return;
912
+ el.style.height = "auto";
913
+ const { height, capped } = nextHeight(el.scrollHeight, maxHeight);
914
+ el.style.height = `${height}px`;
915
+ el.style.overflowY = capped ? "auto" : "hidden";
916
+ }, [ref, maxHeight]);
917
+ useIsomorphicLayoutEffect(resize, [resize, value]);
918
+ (0, import_react14.useEffect)(() => {
919
+ const viewport = window.visualViewport;
920
+ const target = viewport ?? window;
921
+ target.addEventListener("resize", resize);
922
+ return () => target.removeEventListener("resize", resize);
923
+ }, [resize]);
924
+ }
925
+
926
+ // src/pwa/use-is-standalone.ts
927
+ var import_react15 = require("react");
928
+
929
+ // src/pwa/detect.ts
930
+ var IOS_UA = /iPhone|iPad|iPod/;
931
+ var IOS_OTHER_BROWSER = /CriOS|FxiOS|EdgiOS|OPiOS/;
932
+ var IN_APP_WEBVIEW = /FBAN|FBAV|Instagram/;
933
+ var STANDALONE_QUERY = "(display-mode: standalone)";
934
+ function detectPlatform(env) {
935
+ const isIos2 = IOS_UA.test(env.userAgent) || env.platform === "MacIntel" && env.maxTouchPoints > 1;
936
+ const isIosSafari2 = isIos2 && !IOS_OTHER_BROWSER.test(env.userAgent) && !IN_APP_WEBVIEW.test(env.userAgent);
937
+ return { isIos: isIos2, isIosSafari: isIosSafari2, isStandalone: env.standaloneMedia || env.navigatorStandalone };
938
+ }
939
+ function readStandaloneMedia() {
940
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
941
+ return window.matchMedia(STANDALONE_QUERY).matches;
942
+ }
943
+ function readNavigatorStandalone() {
944
+ if (typeof navigator === "undefined") return false;
945
+ return navigator.standalone === true;
946
+ }
947
+ function readPlatform() {
948
+ if (typeof navigator === "undefined") return { isIos: false, isIosSafari: false, isStandalone: false };
949
+ return detectPlatform({
950
+ userAgent: navigator.userAgent ?? "",
951
+ platform: navigator.platform ?? "",
952
+ maxTouchPoints: navigator.maxTouchPoints ?? 0,
953
+ standaloneMedia: readStandaloneMedia(),
954
+ navigatorStandalone: readNavigatorStandalone()
955
+ });
956
+ }
957
+ var isStandalone = () => readPlatform().isStandalone;
958
+ var isIos = () => readPlatform().isIos;
959
+ var isIosSafari = () => readPlatform().isIosSafari;
960
+
961
+ // src/pwa/use-is-standalone.ts
962
+ function useIsStandalone() {
963
+ const subscribeToMode = (0, import_react15.useCallback)((onChange) => subscribe(STANDALONE_QUERY, onChange), []);
964
+ return (0, import_react15.useSyncExternalStore)(subscribeToMode, isStandalone, () => false);
965
+ }
966
+
967
+ // src/pwa/use-install-prompt.ts
968
+ var import_react16 = require("react");
969
+
970
+ // src/pwa/install-store.ts
971
+ var state = { event: null, installed: false };
972
+ var subscribers = /* @__PURE__ */ new Set();
973
+ var permanent = false;
974
+ var holders = 0;
975
+ var attached = false;
976
+ function set(next) {
977
+ state = next;
978
+ subscribers.forEach((fn) => fn());
979
+ }
980
+ function onBeforeInstallPrompt(e) {
981
+ e.preventDefault();
982
+ set({ ...state, event: e });
983
+ }
984
+ function onAppInstalled() {
985
+ set({ event: null, installed: true });
986
+ }
987
+ function sync() {
988
+ const wanted = permanent || holders > 0;
989
+ if (wanted === attached || typeof window === "undefined") return;
990
+ attached = wanted;
991
+ const toggle = wanted ? window.addEventListener : window.removeEventListener;
992
+ toggle.call(window, "beforeinstallprompt", onBeforeInstallPrompt);
993
+ toggle.call(window, "appinstalled", onAppInstalled);
994
+ }
995
+ function startInstallPromptCapture() {
996
+ permanent = true;
997
+ sync();
998
+ }
999
+ function acquireCapture() {
1000
+ holders += 1;
1001
+ sync();
1002
+ let released = false;
1003
+ return () => {
1004
+ if (released) return;
1005
+ released = true;
1006
+ holders -= 1;
1007
+ sync();
1008
+ };
1009
+ }
1010
+ function getInstallState() {
1011
+ return state;
1012
+ }
1013
+ function subscribeInstallState(onChange) {
1014
+ subscribers.add(onChange);
1015
+ return () => {
1016
+ subscribers.delete(onChange);
1017
+ };
1018
+ }
1019
+ async function promptInstall() {
1020
+ const event = state.event;
1021
+ if (!event) return "unavailable";
1022
+ set({ ...state, event: null });
1023
+ try {
1024
+ await event.prompt();
1025
+ const { outcome } = await event.userChoice;
1026
+ return outcome;
1027
+ } catch {
1028
+ return "dismissed";
1029
+ }
1030
+ }
1031
+
1032
+ // src/pwa/use-install-prompt.ts
1033
+ var serverState = { event: null, installed: false };
1034
+ function useInstallPrompt() {
1035
+ const state2 = (0, import_react16.useSyncExternalStore)(subscribeInstallState, getInstallState, () => serverState);
1036
+ const standalone = useIsStandalone();
1037
+ const iosSafari = (0, import_react16.useSyncExternalStore)(
1038
+ () => () => {
1039
+ },
1040
+ isIosSafari,
1041
+ () => false
1042
+ );
1043
+ (0, import_react16.useEffect)(() => acquireCapture(), []);
1044
+ const installed = state2.installed || standalone;
1045
+ return {
1046
+ canPrompt: state2.event !== null && !installed,
1047
+ prompt: promptInstall,
1048
+ installed,
1049
+ needsIosInstructions: iosSafari && !installed
1050
+ };
1051
+ }
1052
+
1053
+ // src/online-status/use-online-status.ts
1054
+ var import_react17 = require("react");
1055
+ function subscribe2(callback) {
1056
+ window.addEventListener("online", callback);
1057
+ window.addEventListener("offline", callback);
1058
+ return () => {
1059
+ window.removeEventListener("online", callback);
1060
+ window.removeEventListener("offline", callback);
1061
+ };
1062
+ }
1063
+ var getSnapshot = () => navigator.onLine;
1064
+ var getServerSnapshot = () => true;
1065
+ function useOnlineStatus() {
1066
+ return (0, import_react17.useSyncExternalStore)(subscribe2, getSnapshot, getServerSnapshot);
1067
+ }
1068
+
1069
+ // src/service-worker/use-service-worker.ts
1070
+ var import_react18 = require("react");
1071
+
1072
+ // src/service-worker/update-state.ts
1073
+ function deriveUpdateState({ hasController: hasController2, waiting, installingState }) {
1074
+ if (!hasController2) return false;
1075
+ return waiting || installingState === "installed";
1076
+ }
1077
+
1078
+ // src/service-worker/register.ts
1079
+ var reloaded = false;
1080
+ function isServiceWorkerSupported() {
1081
+ return typeof navigator !== "undefined" && "serviceWorker" in navigator;
1082
+ }
1083
+ var hasController = () => Boolean(navigator.serviceWorker.controller);
1084
+ async function unregisterAll() {
1085
+ if (!isServiceWorkerSupported()) return;
1086
+ try {
1087
+ const registrations = await navigator.serviceWorker.getRegistrations();
1088
+ await Promise.all(registrations.map((r) => r.unregister()));
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ function registerServiceWorker({ url, scope, onUpdateAvailable }, { reload = () => window.location.reload() } = {}) {
1093
+ let registration;
1094
+ let disposed = false;
1095
+ let applying = false;
1096
+ const cleanups = [];
1097
+ const controlledAtStart = hasController();
1098
+ const onControllerChange = () => {
1099
+ if (!(applying || controlledAtStart) || reloaded) return;
1100
+ reloaded = true;
1101
+ reload();
1102
+ };
1103
+ navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
1104
+ cleanups.push(() => navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange));
1105
+ const watchInstalling = (reg) => {
1106
+ const worker = reg.installing;
1107
+ if (!worker) return;
1108
+ const onState = () => {
1109
+ if (deriveUpdateState({ hasController: hasController(), waiting: false, installingState: worker.state })) {
1110
+ onUpdateAvailable();
1111
+ }
1112
+ };
1113
+ worker.addEventListener("statechange", onState);
1114
+ cleanups.push(() => worker.removeEventListener("statechange", onState));
1115
+ };
1116
+ navigator.serviceWorker.register(url, scope === void 0 ? void 0 : { scope }).then((reg) => {
1117
+ if (disposed) return;
1118
+ registration = reg;
1119
+ if (deriveUpdateState({ hasController: hasController(), waiting: Boolean(reg.waiting) })) {
1120
+ onUpdateAvailable();
1121
+ }
1122
+ const onUpdateFound = () => watchInstalling(reg);
1123
+ reg.addEventListener("updatefound", onUpdateFound);
1124
+ cleanups.push(() => reg.removeEventListener("updatefound", onUpdateFound));
1125
+ }).catch(() => {
1126
+ });
1127
+ return {
1128
+ applyUpdate() {
1129
+ const waiting = registration?.waiting;
1130
+ if (!waiting) return;
1131
+ applying = true;
1132
+ waiting.postMessage({ type: "SKIP_WAITING" });
1133
+ },
1134
+ dispose() {
1135
+ disposed = true;
1136
+ cleanups.splice(0).forEach((fn) => fn());
1137
+ }
1138
+ };
1139
+ }
1140
+
1141
+ // src/service-worker/use-service-worker.ts
1142
+ function useServiceWorker({ url, scope, enabled = true }) {
1143
+ const [supported, setSupported] = (0, import_react18.useState)(true);
1144
+ const [updateAvailable, setUpdateAvailable] = (0, import_react18.useState)(false);
1145
+ const handle = (0, import_react18.useRef)(void 0);
1146
+ (0, import_react18.useEffect)(() => {
1147
+ if (!enabled) return;
1148
+ if (!isServiceWorkerSupported()) {
1149
+ setSupported(false);
1150
+ return;
1151
+ }
1152
+ const registered = registerServiceWorker({ url, scope, onUpdateAvailable: () => setUpdateAvailable(true) });
1153
+ handle.current = registered;
1154
+ return () => {
1155
+ registered.dispose();
1156
+ handle.current = void 0;
1157
+ };
1158
+ }, [url, scope, enabled]);
1159
+ const applyUpdate = (0, import_react18.useCallback)(() => handle.current?.applyUpdate(), []);
1160
+ return { supported, updateAvailable, applyUpdate };
1161
+ }
1162
+
1163
+ // src/share/share-text.ts
1164
+ function shareTextFor({ text, url }) {
1165
+ return [text, url].filter((part) => !!part).join("\n");
1166
+ }
1167
+ function isShareCancel(error) {
1168
+ return typeof error === "object" && error !== null && error.name === "AbortError";
1169
+ }
1170
+
1171
+ // src/share/share-or-copy.ts
1172
+ async function shareOrCopy(data) {
1173
+ if (canShareNatively(data)) {
1174
+ try {
1175
+ await navigator.share(data);
1176
+ return { status: "shared" };
1177
+ } catch (error) {
1178
+ if (isShareCancel(error)) return { status: "cancelled" };
1179
+ }
1180
+ }
1181
+ const result = await copyText(shareTextFor(data));
1182
+ return { status: result.ok ? "copied" : "failed" };
1183
+ }
1184
+ function canShareFiles(files) {
1185
+ if (typeof navigator === "undefined" || typeof navigator.canShare !== "function") return false;
1186
+ try {
1187
+ return navigator.canShare({ files });
1188
+ } catch {
1189
+ return false;
1190
+ }
1191
+ }
1192
+ function canShareNatively(data) {
1193
+ if (typeof navigator === "undefined" || typeof navigator.share !== "function") return false;
1194
+ if (typeof navigator.canShare !== "function") return true;
1195
+ try {
1196
+ return navigator.canShare(data);
1197
+ } catch {
1198
+ return false;
1199
+ }
1200
+ }
1201
+
1202
+ // src/share/use-share-feedback.ts
1203
+ var import_react19 = require("react");
1204
+ function useShareFeedback({ resetMs = 1500 } = {}) {
1205
+ const [status, setStatus] = (0, import_react19.useState)("idle");
1206
+ const timerRef = (0, import_react19.useRef)(void 0);
1207
+ const clearTimer = (0, import_react19.useCallback)(() => {
1208
+ if (timerRef.current !== void 0) clearTimeout(timerRef.current);
1209
+ timerRef.current = void 0;
1210
+ }, []);
1211
+ (0, import_react19.useEffect)(() => clearTimer, [clearTimer]);
1212
+ const share = (0, import_react19.useCallback)(
1213
+ async (data) => {
1214
+ const result = await shareOrCopy(data);
1215
+ clearTimer();
1216
+ setStatus(result.status);
1217
+ timerRef.current = setTimeout(() => setStatus("idle"), resetMs);
1218
+ return result;
1219
+ },
1220
+ [clearTimer, resetMs]
1221
+ );
1222
+ return { share, status };
1223
+ }
1224
+
1225
+ // src/keyboard-inset/use-keyboard-inset.ts
1226
+ var import_react20 = require("react");
1227
+
1228
+ // src/keyboard-inset/compute-inset.ts
1229
+ var DEFAULT_MIN_INSET = 150;
1230
+ function computeKeyboardInset(r) {
1231
+ if (!r.editableFocused) return 0;
1232
+ const occluded = r.layoutHeight - r.visualHeight - r.visualOffsetTop;
1233
+ return occluded >= (r.minInset ?? DEFAULT_MIN_INSET) ? Math.round(occluded) : 0;
1234
+ }
1235
+ var NON_TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
1236
+ "button",
1237
+ "checkbox",
1238
+ "radio",
1239
+ "range",
1240
+ "submit",
1241
+ "reset",
1242
+ "file",
1243
+ "color",
1244
+ "image",
1245
+ "hidden"
1246
+ ]);
1247
+ function isEditableElement(el) {
1248
+ if (!el) return false;
1249
+ const tag = el.tagName.toLowerCase();
1250
+ if (tag === "select") return !el.disabled;
1251
+ if (tag === "textarea" || tag === "input") {
1252
+ const field = el;
1253
+ if (field.readOnly || field.disabled) return false;
1254
+ return tag === "textarea" || !NON_TEXT_INPUT_TYPES.has(field.type);
1255
+ }
1256
+ if (el.isContentEditable === true) return true;
1257
+ const attr = el.getAttribute("contenteditable");
1258
+ return attr !== null && attr !== "false";
1259
+ }
1260
+
1261
+ // src/keyboard-inset/use-keyboard-inset.ts
1262
+ function useKeyboardInset({ minInset = DEFAULT_MIN_INSET, enabled = true } = {}) {
1263
+ const [inset, setInset] = (0, import_react20.useState)(0);
1264
+ (0, import_react20.useEffect)(() => {
1265
+ const vv = typeof window === "undefined" ? null : window.visualViewport;
1266
+ if (!enabled || !vv) {
1267
+ setInset(0);
1268
+ return;
1269
+ }
1270
+ const update = () => setInset(
1271
+ computeKeyboardInset({
1272
+ layoutHeight: window.innerHeight,
1273
+ visualHeight: vv.height,
1274
+ visualOffsetTop: vv.offsetTop,
1275
+ editableFocused: isEditableElement(document.activeElement),
1276
+ minInset
1277
+ })
1278
+ );
1279
+ let frame = 0;
1280
+ const onFocusOut = () => {
1281
+ cancelAnimationFrame(frame);
1282
+ frame = requestAnimationFrame(update);
1283
+ };
1284
+ const onFocusIn = () => {
1285
+ cancelAnimationFrame(frame);
1286
+ update();
1287
+ };
1288
+ vv.addEventListener("resize", update);
1289
+ vv.addEventListener("scroll", update);
1290
+ document.addEventListener("focusin", onFocusIn);
1291
+ document.addEventListener("focusout", onFocusOut);
1292
+ update();
1293
+ return () => {
1294
+ cancelAnimationFrame(frame);
1295
+ vv.removeEventListener("resize", update);
1296
+ vv.removeEventListener("scroll", update);
1297
+ document.removeEventListener("focusin", onFocusIn);
1298
+ document.removeEventListener("focusout", onFocusOut);
1299
+ };
1300
+ }, [enabled, minInset]);
1301
+ return inset;
1302
+ }
1303
+
1304
+ // src/push/use-push-subscription.ts
1305
+ var import_react21 = require("react");
1306
+
1307
+ // src/push/support.ts
1308
+ function getPushSupport(env = readPushEnv()) {
1309
+ if (env.isIos && !env.isStandalone) return "ios-needs-install";
1310
+ if (!env.hasPushManager || !env.hasServiceWorker || !env.hasNotification) return "unsupported";
1311
+ return "supported";
1312
+ }
1313
+ function readPushEnv() {
1314
+ const browser = typeof window !== "undefined" && typeof navigator !== "undefined";
1315
+ return {
1316
+ hasPushManager: browser && "PushManager" in window,
1317
+ hasServiceWorker: browser && "serviceWorker" in navigator,
1318
+ hasNotification: browser && "Notification" in window,
1319
+ isIos: browser && isIos(),
1320
+ isStandalone: browser && isStandalone()
1321
+ };
1322
+ }
1323
+ function readPermission() {
1324
+ if (typeof window === "undefined" || !("Notification" in window)) return "unsupported";
1325
+ return Notification.permission;
1326
+ }
1327
+ function shouldResubscribe({ permission, hasSubscription, resubscribeOnLoad }) {
1328
+ return resubscribeOnLoad && permission === "granted" && !hasSubscription;
1329
+ }
1330
+ function isGone(error) {
1331
+ return typeof error === "object" && error !== null && error.status === 410;
1332
+ }
1333
+
1334
+ // src/push/vapid.ts
1335
+ function urlBase64ToUint8Array(key) {
1336
+ const padding = "=".repeat((4 - key.length % 4) % 4);
1337
+ const base64 = (key + padding).replace(/-/g, "+").replace(/_/g, "/");
1338
+ const raw = atob(base64);
1339
+ const out = new Uint8Array(raw.length);
1340
+ for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
1341
+ return out;
1342
+ }
1343
+
1344
+ // src/push/register.ts
1345
+ function subscribeFresh(reg, vapidPublicKey) {
1346
+ return reg.pushManager.subscribe({
1347
+ userVisibleOnly: true,
1348
+ // lib.dom wants ArrayBufferView<ArrayBuffer>; a Uint8Array satisfies the runtime contract.
1349
+ applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
1350
+ });
1351
+ }
1352
+ async function ensureSubscribed(reg, { vapidPublicKey, onSubscribe }, existing) {
1353
+ let sub = existing ?? await reg.pushManager.getSubscription() ?? await subscribeFresh(reg, vapidPublicKey);
1354
+ try {
1355
+ await onSubscribe(sub.toJSON());
1356
+ } catch (error) {
1357
+ if (!isGone(error)) throw error;
1358
+ await sub.unsubscribe();
1359
+ sub = await subscribeFresh(reg, vapidPublicKey);
1360
+ await onSubscribe(sub.toJSON());
1361
+ }
1362
+ return sub;
1363
+ }
1364
+
1365
+ // src/push/use-push-subscription.ts
1366
+ var messageOf = (error, fallback) => error instanceof Error ? error.message : fallback;
1367
+ function usePushSubscription(options) {
1368
+ const { resubscribeOnLoad = true } = options;
1369
+ const [support, setSupport] = (0, import_react21.useState)("unsupported");
1370
+ const [permission, setPermission] = (0, import_react21.useState)("unsupported");
1371
+ const [subscribed, setSubscribed] = (0, import_react21.useState)(false);
1372
+ const [busy, setBusy] = (0, import_react21.useState)(false);
1373
+ const [error, setError] = (0, import_react21.useState)(null);
1374
+ const latest = (0, import_react21.useRef)(options);
1375
+ latest.current = options;
1376
+ (0, import_react21.useEffect)(() => {
1377
+ const current = getPushSupport();
1378
+ setSupport(current);
1379
+ setPermission(readPermission());
1380
+ if (current !== "supported") return;
1381
+ let cancelled = false;
1382
+ (async () => {
1383
+ try {
1384
+ const reg = await navigator.serviceWorker.ready;
1385
+ const existing = await reg.pushManager.getSubscription();
1386
+ if (cancelled) return;
1387
+ const permissionNow = readPermission();
1388
+ setPermission(permissionNow);
1389
+ if (existing) return setSubscribed(true);
1390
+ if (!shouldResubscribe({ permission: permissionNow, hasSubscription: false, resubscribeOnLoad })) return;
1391
+ await ensureSubscribed(reg, latest.current);
1392
+ if (!cancelled) setSubscribed(true);
1393
+ } catch {
1394
+ }
1395
+ })();
1396
+ return () => {
1397
+ cancelled = true;
1398
+ };
1399
+ }, [resubscribeOnLoad]);
1400
+ const subscribe3 = (0, import_react21.useCallback)(async () => {
1401
+ if (getPushSupport() !== "supported") return;
1402
+ setError(null);
1403
+ setBusy(true);
1404
+ try {
1405
+ const result = await Notification.requestPermission();
1406
+ setPermission(result);
1407
+ if (result !== "granted") return;
1408
+ const reg = await navigator.serviceWorker.ready;
1409
+ await ensureSubscribed(reg, latest.current);
1410
+ setSubscribed(true);
1411
+ } catch (e) {
1412
+ setError(messageOf(e, "subscribe failed"));
1413
+ } finally {
1414
+ setBusy(false);
1415
+ }
1416
+ }, []);
1417
+ const unsubscribe = (0, import_react21.useCallback)(async () => {
1418
+ if (getPushSupport() !== "supported") return;
1419
+ setError(null);
1420
+ setBusy(true);
1421
+ try {
1422
+ const reg = await navigator.serviceWorker.ready;
1423
+ const sub = await reg.pushManager.getSubscription();
1424
+ if (sub) {
1425
+ await latest.current.onUnsubscribe(sub.endpoint);
1426
+ await sub.unsubscribe();
1427
+ }
1428
+ setSubscribed(false);
1429
+ } catch (e) {
1430
+ setError(messageOf(e, "unsubscribe failed"));
1431
+ } finally {
1432
+ setBusy(false);
1433
+ }
1434
+ }, []);
1435
+ return { support, permission, subscribed, busy, error, subscribe: subscribe3, unsubscribe };
1436
+ }
289
1437
  // Annotate the CommonJS export names for ESM import in node:
290
1438
  0 && (module.exports = {
291
1439
  BACK_ZONE_PX,
292
1440
  CLOSE_DISTANCE_PX,
1441
+ COARSE_POINTER_QUERY,
1442
+ DEFAULT_IGNORE_SELECTOR,
293
1443
  FOCUSABLE_SELECTOR,
294
1444
  FORWARD_ZONE_PX,
295
1445
  MAX_SWIPE_DURATION_MS,
296
1446
  OPEN_DISTANCE_PX,
1447
+ REDUCED_MOTION_QUERY,
297
1448
  VERTICAL_CANCEL_PX,
1449
+ canShareFiles,
1450
+ computeKeyboardInset,
298
1451
  copyText,
1452
+ createDebouncer,
1453
+ deriveUpdateState,
1454
+ detectApple,
1455
+ detectPlatform,
299
1456
  focusableWithin,
1457
+ getPushSupport,
1458
+ handleBeforeUnload,
1459
+ isEditableElement,
1460
+ isEditableTarget,
1461
+ isInIgnoredRegion,
300
1462
  isInOpenZone,
301
1463
  isInsideHorizontalScroller,
1464
+ isIos,
1465
+ isIosSafari,
1466
+ isOutside,
1467
+ isPrimaryPress,
1468
+ isShareCancel,
1469
+ isStandalone,
1470
+ lockScroll,
1471
+ matchHotkey,
1472
+ nextDelay,
302
1473
  nextFocusTarget,
1474
+ nextHeight,
1475
+ parseHotkey,
1476
+ readText,
303
1477
  resolveSwipeIntent,
1478
+ shareOrCopy,
1479
+ shareTextFor,
1480
+ shouldResubscribe,
1481
+ startInstallPromptCapture,
1482
+ unlockScroll,
1483
+ unregisterAll,
1484
+ urlBase64ToUint8Array,
1485
+ useAutoGrowTextarea,
1486
+ useCoarsePointer,
304
1487
  useCopyFeedback,
1488
+ useDebouncedCallback,
1489
+ useDebouncedValue,
305
1490
  useDrawerSwipe,
306
1491
  useEscape,
307
- useFocusTrap
1492
+ useFocusTrap,
1493
+ useHotkey,
1494
+ useInstallPrompt,
1495
+ useIsStandalone,
1496
+ useKeyboardInset,
1497
+ useMediaQuery,
1498
+ useOnlineStatus,
1499
+ useOutsidePress,
1500
+ usePushSubscription,
1501
+ useReducedMotion,
1502
+ useScrollLock,
1503
+ useServiceWorker,
1504
+ useShareFeedback,
1505
+ useStorageState,
1506
+ useUnsavedChangesGuard,
1507
+ useVisibilityPolling
308
1508
  });