@develemit/web-hooks 0.2.0 → 0.3.1

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 (57) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +157 -0
  3. package/dist/chunk-4PVVTQ5V.js +74 -0
  4. package/dist/{chunk-OZ4PU4E3.js → chunk-5XRVPZHD.js} +14 -4
  5. package/dist/{chunk-3UGRXQIA.js → chunk-DQBKU7HI.js} +10 -3
  6. package/dist/chunk-EXEJI4FK.js +43 -0
  7. package/dist/{chunk-3CACI4OQ.js → chunk-FE3BQD2I.js} +1 -25
  8. package/dist/chunk-HTVNN2NM.js +29 -0
  9. package/dist/chunk-R2PFNE3U.js +154 -0
  10. package/dist/chunk-TNEJ34T5.js +86 -0
  11. package/dist/chunk-UVJBJETY.js +121 -0
  12. package/dist/{chunk-UZADAINO.js → chunk-WVM2LTWQ.js} +2 -21
  13. package/dist/chunk-WXL22EWK.js +21 -0
  14. package/dist/chunk-YFMMSLMB.js +41 -0
  15. package/dist/chunk-Z3X27PCA.js +108 -0
  16. package/dist/clipboard.cjs +13 -0
  17. package/dist/clipboard.d.cts +10 -1
  18. package/dist/clipboard.d.ts +10 -1
  19. package/dist/clipboard.js +6 -2
  20. package/dist/drawer-swipe.cjs +16 -4
  21. package/dist/drawer-swipe.d.cts +7 -3
  22. package/dist/drawer-swipe.d.ts +7 -3
  23. package/dist/drawer-swipe.js +5 -1
  24. package/dist/index.cjs +637 -19
  25. package/dist/index.d.cts +8 -2
  26. package/dist/index.d.ts +8 -2
  27. package/dist/index.js +77 -7
  28. package/dist/keyboard-inset.cjs +113 -0
  29. package/dist/keyboard-inset.d.cts +25 -0
  30. package/dist/keyboard-inset.d.ts +25 -0
  31. package/dist/keyboard-inset.js +11 -0
  32. package/dist/media-query.js +5 -3
  33. package/dist/online-status.cjs +46 -0
  34. package/dist/online-status.d.cts +3 -0
  35. package/dist/online-status.d.ts +3 -0
  36. package/dist/online-status.js +7 -0
  37. package/dist/outside-press.cjs +10 -3
  38. package/dist/outside-press.d.cts +4 -2
  39. package/dist/outside-press.d.ts +4 -2
  40. package/dist/outside-press.js +1 -1
  41. package/dist/push.cjs +209 -0
  42. package/dist/push.d.cts +38 -0
  43. package/dist/push.d.ts +38 -0
  44. package/dist/push.js +14 -0
  45. package/dist/pwa.cjs +198 -0
  46. package/dist/pwa.d.cts +35 -0
  47. package/dist/pwa.d.ts +35 -0
  48. package/dist/pwa.js +22 -0
  49. package/dist/service-worker.cjs +148 -0
  50. package/dist/service-worker.d.cts +24 -0
  51. package/dist/service-worker.d.ts +24 -0
  52. package/dist/service-worker.js +11 -0
  53. package/dist/share.cjs +139 -0
  54. package/dist/share.d.cts +23 -0
  55. package/dist/share.d.ts +23 -0
  56. package/dist/share.js +16 -0
  57. package/package.json +31 -1
package/dist/index.cjs CHANGED
@@ -24,30 +24,49 @@ __export(src_exports, {
24
24
  BACK_ZONE_PX: () => BACK_ZONE_PX,
25
25
  CLOSE_DISTANCE_PX: () => CLOSE_DISTANCE_PX,
26
26
  COARSE_POINTER_QUERY: () => COARSE_POINTER_QUERY,
27
+ DEFAULT_IGNORE_SELECTOR: () => DEFAULT_IGNORE_SELECTOR,
27
28
  FOCUSABLE_SELECTOR: () => FOCUSABLE_SELECTOR,
28
29
  FORWARD_ZONE_PX: () => FORWARD_ZONE_PX,
29
30
  MAX_SWIPE_DURATION_MS: () => MAX_SWIPE_DURATION_MS,
30
31
  OPEN_DISTANCE_PX: () => OPEN_DISTANCE_PX,
31
32
  REDUCED_MOTION_QUERY: () => REDUCED_MOTION_QUERY,
32
33
  VERTICAL_CANCEL_PX: () => VERTICAL_CANCEL_PX,
34
+ canShareFiles: () => canShareFiles,
35
+ computeKeyboardInset: () => computeKeyboardInset,
33
36
  copyText: () => copyText,
34
37
  createDebouncer: () => createDebouncer,
38
+ deriveUpdateState: () => deriveUpdateState,
35
39
  detectApple: () => detectApple,
40
+ detectPlatform: () => detectPlatform,
36
41
  focusableWithin: () => focusableWithin,
42
+ getPushSupport: () => getPushSupport,
37
43
  handleBeforeUnload: () => handleBeforeUnload,
44
+ isEditableElement: () => isEditableElement,
38
45
  isEditableTarget: () => isEditableTarget,
46
+ isInIgnoredRegion: () => isInIgnoredRegion,
39
47
  isInOpenZone: () => isInOpenZone,
40
48
  isInsideHorizontalScroller: () => isInsideHorizontalScroller,
49
+ isIos: () => isIos,
50
+ isIosSafari: () => isIosSafari,
41
51
  isOutside: () => isOutside,
42
52
  isPrimaryPress: () => isPrimaryPress,
53
+ isShareCancel: () => isShareCancel,
54
+ isStandalone: () => isStandalone,
43
55
  lockScroll: () => lockScroll,
44
56
  matchHotkey: () => matchHotkey,
45
57
  nextDelay: () => nextDelay,
46
58
  nextFocusTarget: () => nextFocusTarget,
47
59
  nextHeight: () => nextHeight,
48
60
  parseHotkey: () => parseHotkey,
61
+ readText: () => readText,
49
62
  resolveSwipeIntent: () => resolveSwipeIntent,
63
+ shareOrCopy: () => shareOrCopy,
64
+ shareTextFor: () => shareTextFor,
65
+ shouldResubscribe: () => shouldResubscribe,
66
+ startInstallPromptCapture: () => startInstallPromptCapture,
50
67
  unlockScroll: () => unlockScroll,
68
+ unregisterAll: () => unregisterAll,
69
+ urlBase64ToUint8Array: () => urlBase64ToUint8Array,
51
70
  useAutoGrowTextarea: () => useAutoGrowTextarea,
52
71
  useCoarsePointer: () => useCoarsePointer,
53
72
  useCopyFeedback: () => useCopyFeedback,
@@ -57,10 +76,17 @@ __export(src_exports, {
57
76
  useEscape: () => useEscape,
58
77
  useFocusTrap: () => useFocusTrap,
59
78
  useHotkey: () => useHotkey,
79
+ useInstallPrompt: () => useInstallPrompt,
80
+ useIsStandalone: () => useIsStandalone,
81
+ useKeyboardInset: () => useKeyboardInset,
60
82
  useMediaQuery: () => useMediaQuery,
83
+ useOnlineStatus: () => useOnlineStatus,
61
84
  useOutsidePress: () => useOutsidePress,
85
+ usePushSubscription: () => usePushSubscription,
62
86
  useReducedMotion: () => useReducedMotion,
63
87
  useScrollLock: () => useScrollLock,
88
+ useServiceWorker: () => useServiceWorker,
89
+ useShareFeedback: () => useShareFeedback,
64
90
  useStorageState: () => useStorageState,
65
91
  useUnsavedChangesGuard: () => useUnsavedChangesGuard,
66
92
  useVisibilityPolling: () => useVisibilityPolling
@@ -78,7 +104,8 @@ var HORIZONTAL_DOMINANCE = 1.5;
78
104
  function isInOpenZone(x, viewportWidth) {
79
105
  return x > BACK_ZONE_PX && x < viewportWidth - FORWARD_ZONE_PX;
80
106
  }
81
- function resolveSwipeIntent(start, end, drawerOpen, viewportWidth) {
107
+ function resolveSwipeIntent(start, end, drawerOpen, viewportWidth, startedInIgnoredRegion = false) {
108
+ if (startedInIgnoredRegion) return null;
82
109
  const dx = end.x - start.x;
83
110
  const dy = end.y - start.y;
84
111
  if (end.time - start.time > MAX_SWIPE_DURATION_MS) return null;
@@ -100,6 +127,11 @@ function isInsideHorizontalScroller(target) {
100
127
  }
101
128
  return false;
102
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
+ }
103
135
 
104
136
  // src/drawer-swipe/use-drawer-swipe.ts
105
137
  var import_react = require("react");
@@ -111,17 +143,19 @@ function useDrawerSwipe({
111
143
  onOpen,
112
144
  onClose,
113
145
  enabled = true,
114
- maxViewportWidth = 768
146
+ maxViewportWidth = 768,
147
+ ignoreSelector = DEFAULT_IGNORE_SELECTOR
115
148
  }) {
116
149
  const start = (0, import_react.useRef)(null);
117
- const handlers = (0, import_react.useRef)({ drawerOpen, onOpen, onClose, maxViewportWidth });
118
- 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 };
119
152
  (0, import_react.useEffect)(() => {
120
153
  if (!enabled) return;
121
154
  function handleStart(event) {
122
155
  start.current = null;
123
156
  if (event.touches.length !== 1) return;
124
157
  if (isWideViewport(handlers.current.maxViewportWidth)) return;
158
+ if (isInIgnoredRegion(event.target, handlers.current.ignoreSelector)) return;
125
159
  if (!handlers.current.drawerOpen && isInsideHorizontalScroller(event.target)) {
126
160
  return;
127
161
  }
@@ -322,6 +356,17 @@ function useCopyFeedback({ resetMs = 1500 } = {}) {
322
356
  return { copy, status };
323
357
  }
324
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
+
325
370
  // src/scroll-lock/use-scroll-lock.ts
326
371
  var import_react5 = require("react");
327
372
 
@@ -436,9 +481,9 @@ function subscribe(query, onChange) {
436
481
  function useMediaQuery(query, options = {}) {
437
482
  const serverDefault = options.serverDefault ?? false;
438
483
  const subscribeToQuery = (0, import_react6.useCallback)((onChange) => subscribe(query, onChange), [query]);
439
- const getSnapshot = (0, import_react6.useCallback)(() => getMatches(query) ?? serverDefault, [query, serverDefault]);
440
- const getServerSnapshot = (0, import_react6.useCallback)(() => serverDefault, [serverDefault]);
441
- return (0, import_react6.useSyncExternalStore)(subscribeToQuery, getSnapshot, getServerSnapshot);
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);
442
487
  }
443
488
  function useReducedMotion(options) {
444
489
  return useMediaQuery(REDUCED_MOTION_QUERY, options);
@@ -448,21 +493,28 @@ function useCoarsePointer(options) {
448
493
  }
449
494
 
450
495
  // src/outside-press/outside-press.ts
451
- function isOutside(target, elements) {
496
+ function isOutside(target, elements, ignoreSelector) {
452
497
  if (!target) return false;
453
498
  if (!target.isConnected) return false;
499
+ if (ignoreSelector && isInIgnoredRegion2(target, ignoreSelector)) return false;
454
500
  return !elements.some((el) => el?.contains(target));
455
501
  }
456
502
  function isPrimaryPress(event) {
457
503
  return !(event.pointerType === "mouse" && event.button !== 0);
458
504
  }
505
+ function isInIgnoredRegion2(target, selector) {
506
+ const element = target instanceof Element ? target : target.parentElement;
507
+ return element?.closest(selector) != null;
508
+ }
459
509
 
460
510
  // src/outside-press/use-outside-press.ts
461
511
  var import_react7 = require("react");
462
- function useOutsidePress(refs, onOutsidePress, { enabled = true } = {}) {
512
+ function useOutsidePress(refs, onOutsidePress, { enabled = true, ignore } = {}) {
463
513
  const refsRef = (0, import_react7.useRef)(refs);
464
514
  const handlerRef = (0, import_react7.useRef)(onOutsidePress);
515
+ const ignoreRef = (0, import_react7.useRef)(ignore);
465
516
  (0, import_react7.useEffect)(() => {
517
+ ignoreRef.current = ignore;
466
518
  refsRef.current = refs;
467
519
  handlerRef.current = onOutsidePress;
468
520
  });
@@ -472,7 +524,7 @@ function useOutsidePress(refs, onOutsidePress, { enabled = true } = {}) {
472
524
  if (!isPrimaryPress(event)) return;
473
525
  const list = Array.isArray(refsRef.current) ? refsRef.current : [refsRef.current];
474
526
  const elements = list.map((ref) => ref.current);
475
- if (isOutside(event.target, elements)) handlerRef.current(event);
527
+ if (isOutside(event.target, elements, ignoreRef.current)) handlerRef.current(event);
476
528
  }
477
529
  document.addEventListener("pointerdown", onPointerDown, true);
478
530
  return () => document.removeEventListener("pointerdown", onPointerDown, true);
@@ -690,9 +742,9 @@ function onStorageEvent(event) {
690
742
  function subscribeToKey(kind, key, listener) {
691
743
  const id = slot(kind, key);
692
744
  const wasEmpty = listeners.size === 0;
693
- let set = listeners.get(id);
694
- if (!set) listeners.set(id, set = /* @__PURE__ */ new Set());
695
- set.add(listener);
745
+ let set2 = listeners.get(id);
746
+ if (!set2) listeners.set(id, set2 = /* @__PURE__ */ new Set());
747
+ set2.add(listener);
696
748
  if (wasEmpty) window.addEventListener("storage", onStorageEvent);
697
749
  return () => {
698
750
  const current = listeners.get(id);
@@ -712,8 +764,8 @@ function useStorageState(key, defaultValue, options = {}) {
712
764
  latest.current = { serialize, parse, defaultValue };
713
765
  });
714
766
  const cache = (0, import_react11.useRef)(null);
715
- const subscribe2 = (0, import_react11.useCallback)((onChange) => subscribeToKey(kind, key, onChange), [kind, key]);
716
- const getSnapshot = (0, import_react11.useCallback)(() => {
767
+ const subscribe3 = (0, import_react11.useCallback)((onChange) => subscribeToKey(kind, key, onChange), [kind, key]);
768
+ const getSnapshot2 = (0, import_react11.useCallback)(() => {
717
769
  const raw = readCurrent(kind, key);
718
770
  const hit = cache.current;
719
771
  if (hit && hit.kind === kind && hit.key === key && hit.raw === raw && hit.parse === parse) return hit.value;
@@ -721,9 +773,9 @@ function useStorageState(key, defaultValue, options = {}) {
721
773
  cache.current = { kind, key, raw, parse, value: value2 };
722
774
  return value2;
723
775
  }, [kind, key, parse, defaultValue]);
724
- const getServerSnapshot = (0, import_react11.useCallback)(() => defaultValue, [defaultValue]);
725
- const value = (0, import_react11.useSyncExternalStore)(subscribe2, getSnapshot, getServerSnapshot);
726
- const set = (0, import_react11.useCallback)(
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)(
727
779
  (next) => {
728
780
  const { serialize: encode, parse: decode, defaultValue: fallback } = latest.current;
729
781
  try {
@@ -735,7 +787,7 @@ function useStorageState(key, defaultValue, options = {}) {
735
787
  [kind, key]
736
788
  );
737
789
  const remove = (0, import_react11.useCallback)(() => removeCurrent(kind, key), [kind, key]);
738
- return [value, set, remove];
790
+ return [value, set2, remove];
739
791
  }
740
792
 
741
793
  // src/unsaved-guard/handle-before-unload.ts
@@ -870,35 +922,594 @@ function useAutoGrowTextarea(ref, value, { maxHeight } = {}) {
870
922
  return () => target.removeEventListener("resize", resize);
871
923
  }, [resize]);
872
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, onError }, { reload = () => window.location.reload() } = {}) {
1093
+ let registration;
1094
+ let disposed = false;
1095
+ let applying = false;
1096
+ const cleanups = [];
1097
+ const controlledAtStart = hasController();
1098
+ let activatedUnasked = false;
1099
+ const reloadOnce = () => {
1100
+ if (reloaded) return;
1101
+ reloaded = true;
1102
+ reload();
1103
+ };
1104
+ const onControllerChange = () => {
1105
+ if (applying) return reloadOnce();
1106
+ if (!controlledAtStart || activatedUnasked) return;
1107
+ activatedUnasked = true;
1108
+ onUpdateAvailable();
1109
+ };
1110
+ navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
1111
+ cleanups.push(() => navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange));
1112
+ const watchInstalling = (reg) => {
1113
+ const worker = reg.installing;
1114
+ if (!worker) return;
1115
+ const onState = () => {
1116
+ if (deriveUpdateState({ hasController: hasController(), waiting: false, installingState: worker.state })) {
1117
+ onUpdateAvailable();
1118
+ }
1119
+ };
1120
+ worker.addEventListener("statechange", onState);
1121
+ cleanups.push(() => worker.removeEventListener("statechange", onState));
1122
+ };
1123
+ navigator.serviceWorker.register(url, scope === void 0 ? void 0 : { scope }).then((reg) => {
1124
+ if (disposed) return;
1125
+ registration = reg;
1126
+ if (deriveUpdateState({ hasController: hasController(), waiting: Boolean(reg.waiting) })) {
1127
+ onUpdateAvailable();
1128
+ }
1129
+ const onUpdateFound = () => watchInstalling(reg);
1130
+ reg.addEventListener("updatefound", onUpdateFound);
1131
+ cleanups.push(() => reg.removeEventListener("updatefound", onUpdateFound));
1132
+ }).catch((error) => {
1133
+ if (disposed) return;
1134
+ try {
1135
+ onError?.(error);
1136
+ } catch {
1137
+ }
1138
+ });
1139
+ return {
1140
+ applyUpdate() {
1141
+ if (activatedUnasked) return reloadOnce();
1142
+ const waiting = registration?.waiting;
1143
+ if (!waiting) return;
1144
+ applying = true;
1145
+ waiting.postMessage({ type: "SKIP_WAITING" });
1146
+ },
1147
+ dispose() {
1148
+ disposed = true;
1149
+ cleanups.splice(0).forEach((fn) => fn());
1150
+ }
1151
+ };
1152
+ }
1153
+
1154
+ // src/service-worker/use-service-worker.ts
1155
+ function useServiceWorker({ url, scope, enabled = true, onError }) {
1156
+ const [supported, setSupported] = (0, import_react18.useState)(true);
1157
+ const [updateAvailable, setUpdateAvailable] = (0, import_react18.useState)(false);
1158
+ const onErrorRef = (0, import_react18.useRef)(onError);
1159
+ onErrorRef.current = onError;
1160
+ const handle = (0, import_react18.useRef)(void 0);
1161
+ (0, import_react18.useEffect)(() => {
1162
+ if (!enabled) return;
1163
+ if (!isServiceWorkerSupported()) {
1164
+ setSupported(false);
1165
+ return;
1166
+ }
1167
+ const registered = registerServiceWorker({
1168
+ url,
1169
+ scope,
1170
+ onUpdateAvailable: () => setUpdateAvailable(true),
1171
+ onError: (error) => onErrorRef.current?.(error)
1172
+ });
1173
+ handle.current = registered;
1174
+ return () => {
1175
+ registered.dispose();
1176
+ handle.current = void 0;
1177
+ };
1178
+ }, [url, scope, enabled]);
1179
+ const applyUpdate = (0, import_react18.useCallback)(() => handle.current?.applyUpdate(), []);
1180
+ return { supported, updateAvailable, applyUpdate };
1181
+ }
1182
+
1183
+ // src/share/share-text.ts
1184
+ function shareTextFor({ text, url }) {
1185
+ return [text, url].filter((part) => !!part).join("\n");
1186
+ }
1187
+ function isShareCancel(error) {
1188
+ return typeof error === "object" && error !== null && error.name === "AbortError";
1189
+ }
1190
+
1191
+ // src/share/share-or-copy.ts
1192
+ async function shareOrCopy(data) {
1193
+ if (canShareNatively(data)) {
1194
+ try {
1195
+ await navigator.share(data);
1196
+ return { status: "shared" };
1197
+ } catch (error) {
1198
+ if (isShareCancel(error)) return { status: "cancelled" };
1199
+ }
1200
+ }
1201
+ const result = await copyText(shareTextFor(data));
1202
+ return { status: result.ok ? "copied" : "failed" };
1203
+ }
1204
+ function canShareFiles(files) {
1205
+ if (typeof navigator === "undefined" || typeof navigator.canShare !== "function") return false;
1206
+ try {
1207
+ return navigator.canShare({ files });
1208
+ } catch {
1209
+ return false;
1210
+ }
1211
+ }
1212
+ function canShareNatively(data) {
1213
+ if (typeof navigator === "undefined" || typeof navigator.share !== "function") return false;
1214
+ if (typeof navigator.canShare !== "function") return true;
1215
+ try {
1216
+ return navigator.canShare(data);
1217
+ } catch {
1218
+ return false;
1219
+ }
1220
+ }
1221
+
1222
+ // src/share/use-share-feedback.ts
1223
+ var import_react19 = require("react");
1224
+ function useShareFeedback({ resetMs = 1500 } = {}) {
1225
+ const [status, setStatus] = (0, import_react19.useState)("idle");
1226
+ const timerRef = (0, import_react19.useRef)(void 0);
1227
+ const clearTimer = (0, import_react19.useCallback)(() => {
1228
+ if (timerRef.current !== void 0) clearTimeout(timerRef.current);
1229
+ timerRef.current = void 0;
1230
+ }, []);
1231
+ (0, import_react19.useEffect)(() => clearTimer, [clearTimer]);
1232
+ const share = (0, import_react19.useCallback)(
1233
+ async (data) => {
1234
+ const result = await shareOrCopy(data);
1235
+ clearTimer();
1236
+ setStatus(result.status);
1237
+ timerRef.current = setTimeout(() => setStatus("idle"), resetMs);
1238
+ return result;
1239
+ },
1240
+ [clearTimer, resetMs]
1241
+ );
1242
+ return { share, status };
1243
+ }
1244
+
1245
+ // src/keyboard-inset/use-keyboard-inset.ts
1246
+ var import_react20 = require("react");
1247
+
1248
+ // src/keyboard-inset/compute-inset.ts
1249
+ var DEFAULT_MIN_INSET = 150;
1250
+ function computeKeyboardInset(r) {
1251
+ if (!r.editableFocused) return 0;
1252
+ const occluded = r.layoutHeight - r.visualHeight - r.visualOffsetTop;
1253
+ return occluded >= (r.minInset ?? DEFAULT_MIN_INSET) ? Math.round(occluded) : 0;
1254
+ }
1255
+ var NON_TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
1256
+ "button",
1257
+ "checkbox",
1258
+ "radio",
1259
+ "range",
1260
+ "submit",
1261
+ "reset",
1262
+ "file",
1263
+ "color",
1264
+ "image",
1265
+ "hidden"
1266
+ ]);
1267
+ function isEditableElement(el) {
1268
+ if (!el) return false;
1269
+ const tag = el.tagName.toLowerCase();
1270
+ if (tag === "select") return !el.disabled;
1271
+ if (tag === "textarea" || tag === "input") {
1272
+ const field = el;
1273
+ if (field.readOnly || field.disabled) return false;
1274
+ return tag === "textarea" || !NON_TEXT_INPUT_TYPES.has(field.type);
1275
+ }
1276
+ if (el.isContentEditable === true) return true;
1277
+ const attr = el.getAttribute("contenteditable");
1278
+ return attr !== null && attr !== "false";
1279
+ }
1280
+
1281
+ // src/keyboard-inset/use-keyboard-inset.ts
1282
+ function useKeyboardInset({ minInset = DEFAULT_MIN_INSET, enabled = true } = {}) {
1283
+ const [inset, setInset] = (0, import_react20.useState)(0);
1284
+ (0, import_react20.useEffect)(() => {
1285
+ const vv = typeof window === "undefined" ? null : window.visualViewport;
1286
+ if (!enabled || !vv) {
1287
+ setInset(0);
1288
+ return;
1289
+ }
1290
+ const update = () => setInset(
1291
+ computeKeyboardInset({
1292
+ layoutHeight: window.innerHeight,
1293
+ visualHeight: vv.height,
1294
+ visualOffsetTop: vv.offsetTop,
1295
+ editableFocused: isEditableElement(document.activeElement),
1296
+ minInset
1297
+ })
1298
+ );
1299
+ let frame = 0;
1300
+ const onFocusOut = () => {
1301
+ cancelAnimationFrame(frame);
1302
+ frame = requestAnimationFrame(update);
1303
+ };
1304
+ const onFocusIn = () => {
1305
+ cancelAnimationFrame(frame);
1306
+ update();
1307
+ };
1308
+ vv.addEventListener("resize", update);
1309
+ vv.addEventListener("scroll", update);
1310
+ document.addEventListener("focusin", onFocusIn);
1311
+ document.addEventListener("focusout", onFocusOut);
1312
+ update();
1313
+ return () => {
1314
+ cancelAnimationFrame(frame);
1315
+ vv.removeEventListener("resize", update);
1316
+ vv.removeEventListener("scroll", update);
1317
+ document.removeEventListener("focusin", onFocusIn);
1318
+ document.removeEventListener("focusout", onFocusOut);
1319
+ };
1320
+ }, [enabled, minInset]);
1321
+ return inset;
1322
+ }
1323
+
1324
+ // src/push/use-push-subscription.ts
1325
+ var import_react21 = require("react");
1326
+
1327
+ // src/push/support.ts
1328
+ function getPushSupport(env = readPushEnv()) {
1329
+ if (env.isIos && !env.isStandalone) return "ios-needs-install";
1330
+ if (!env.hasPushManager || !env.hasServiceWorker || !env.hasNotification) return "unsupported";
1331
+ return "supported";
1332
+ }
1333
+ function readPushEnv() {
1334
+ const browser = typeof window !== "undefined" && typeof navigator !== "undefined";
1335
+ return {
1336
+ hasPushManager: browser && "PushManager" in window,
1337
+ hasServiceWorker: browser && "serviceWorker" in navigator,
1338
+ hasNotification: browser && "Notification" in window,
1339
+ isIos: browser && isIos(),
1340
+ isStandalone: browser && isStandalone()
1341
+ };
1342
+ }
1343
+ function readPermission() {
1344
+ if (typeof window === "undefined" || !("Notification" in window)) return "unsupported";
1345
+ return Notification.permission;
1346
+ }
1347
+ function isKeyReady(key) {
1348
+ return key.trim().length > 0;
1349
+ }
1350
+ function shouldResubscribe({ permission, hasSubscription, resubscribeOnLoad, hasKey = true }) {
1351
+ return hasKey && resubscribeOnLoad && permission === "granted" && !hasSubscription;
1352
+ }
1353
+ function isGone(error) {
1354
+ return typeof error === "object" && error !== null && error.status === 410;
1355
+ }
1356
+
1357
+ // src/push/vapid.ts
1358
+ function urlBase64ToUint8Array(key) {
1359
+ const padding = "=".repeat((4 - key.length % 4) % 4);
1360
+ const base64 = (key + padding).replace(/-/g, "+").replace(/_/g, "/");
1361
+ const raw = atob(base64);
1362
+ const out = new Uint8Array(raw.length);
1363
+ for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
1364
+ return out;
1365
+ }
1366
+
1367
+ // src/push/register.ts
1368
+ function subscribeFresh(reg, vapidPublicKey) {
1369
+ return reg.pushManager.subscribe({
1370
+ userVisibleOnly: true,
1371
+ // lib.dom wants ArrayBufferView<ArrayBuffer>; a Uint8Array satisfies the runtime contract.
1372
+ applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
1373
+ });
1374
+ }
1375
+ async function ensureSubscribed(reg, { vapidPublicKey, onSubscribe }, existing) {
1376
+ let sub = existing ?? await reg.pushManager.getSubscription() ?? await subscribeFresh(reg, vapidPublicKey);
1377
+ try {
1378
+ await onSubscribe(sub.toJSON());
1379
+ } catch (error) {
1380
+ if (!isGone(error)) throw error;
1381
+ await sub.unsubscribe();
1382
+ sub = await subscribeFresh(reg, vapidPublicKey);
1383
+ await onSubscribe(sub.toJSON());
1384
+ }
1385
+ return sub;
1386
+ }
1387
+
1388
+ // src/push/use-push-subscription.ts
1389
+ var messageOf = (error, fallback) => error instanceof Error ? error.message : fallback;
1390
+ function usePushSubscription(options) {
1391
+ const { resubscribeOnLoad = true } = options;
1392
+ const [support, setSupport] = (0, import_react21.useState)("unsupported");
1393
+ const [permission, setPermission] = (0, import_react21.useState)("unsupported");
1394
+ const [subscribed, setSubscribed] = (0, import_react21.useState)(false);
1395
+ const [busy, setBusy] = (0, import_react21.useState)(false);
1396
+ const [error, setError] = (0, import_react21.useState)(null);
1397
+ const hasKey = isKeyReady(options.vapidPublicKey);
1398
+ const latest = (0, import_react21.useRef)(options);
1399
+ latest.current = options;
1400
+ (0, import_react21.useEffect)(() => {
1401
+ const current = getPushSupport();
1402
+ setSupport(current);
1403
+ setPermission(readPermission());
1404
+ if (current !== "supported") return;
1405
+ let cancelled = false;
1406
+ (async () => {
1407
+ try {
1408
+ const reg = await navigator.serviceWorker.ready;
1409
+ const existing = await reg.pushManager.getSubscription();
1410
+ if (cancelled) return;
1411
+ const permissionNow = readPermission();
1412
+ setPermission(permissionNow);
1413
+ if (existing) return setSubscribed(true);
1414
+ if (!shouldResubscribe({ permission: permissionNow, hasSubscription: false, resubscribeOnLoad, hasKey })) return;
1415
+ await ensureSubscribed(reg, latest.current);
1416
+ if (!cancelled) setSubscribed(true);
1417
+ } catch {
1418
+ }
1419
+ })();
1420
+ return () => {
1421
+ cancelled = true;
1422
+ };
1423
+ }, [resubscribeOnLoad, hasKey]);
1424
+ const subscribe3 = (0, import_react21.useCallback)(async () => {
1425
+ if (getPushSupport() !== "supported") return;
1426
+ if (!isKeyReady(latest.current.vapidPublicKey)) {
1427
+ setError("push key not ready");
1428
+ return;
1429
+ }
1430
+ setError(null);
1431
+ setBusy(true);
1432
+ try {
1433
+ const result = await Notification.requestPermission();
1434
+ setPermission(result);
1435
+ if (result !== "granted") return;
1436
+ const reg = await navigator.serviceWorker.ready;
1437
+ await ensureSubscribed(reg, latest.current);
1438
+ setSubscribed(true);
1439
+ } catch (e) {
1440
+ setError(messageOf(e, "subscribe failed"));
1441
+ } finally {
1442
+ setBusy(false);
1443
+ }
1444
+ }, []);
1445
+ const unsubscribe = (0, import_react21.useCallback)(async () => {
1446
+ if (getPushSupport() !== "supported") return;
1447
+ setError(null);
1448
+ setBusy(true);
1449
+ try {
1450
+ const reg = await navigator.serviceWorker.ready;
1451
+ const sub = await reg.pushManager.getSubscription();
1452
+ if (sub) {
1453
+ await latest.current.onUnsubscribe(sub.endpoint);
1454
+ await sub.unsubscribe();
1455
+ }
1456
+ setSubscribed(false);
1457
+ } catch (e) {
1458
+ setError(messageOf(e, "unsubscribe failed"));
1459
+ } finally {
1460
+ setBusy(false);
1461
+ }
1462
+ }, []);
1463
+ return { support, permission, subscribed, busy, error, subscribe: subscribe3, unsubscribe };
1464
+ }
873
1465
  // Annotate the CommonJS export names for ESM import in node:
874
1466
  0 && (module.exports = {
875
1467
  BACK_ZONE_PX,
876
1468
  CLOSE_DISTANCE_PX,
877
1469
  COARSE_POINTER_QUERY,
1470
+ DEFAULT_IGNORE_SELECTOR,
878
1471
  FOCUSABLE_SELECTOR,
879
1472
  FORWARD_ZONE_PX,
880
1473
  MAX_SWIPE_DURATION_MS,
881
1474
  OPEN_DISTANCE_PX,
882
1475
  REDUCED_MOTION_QUERY,
883
1476
  VERTICAL_CANCEL_PX,
1477
+ canShareFiles,
1478
+ computeKeyboardInset,
884
1479
  copyText,
885
1480
  createDebouncer,
1481
+ deriveUpdateState,
886
1482
  detectApple,
1483
+ detectPlatform,
887
1484
  focusableWithin,
1485
+ getPushSupport,
888
1486
  handleBeforeUnload,
1487
+ isEditableElement,
889
1488
  isEditableTarget,
1489
+ isInIgnoredRegion,
890
1490
  isInOpenZone,
891
1491
  isInsideHorizontalScroller,
1492
+ isIos,
1493
+ isIosSafari,
892
1494
  isOutside,
893
1495
  isPrimaryPress,
1496
+ isShareCancel,
1497
+ isStandalone,
894
1498
  lockScroll,
895
1499
  matchHotkey,
896
1500
  nextDelay,
897
1501
  nextFocusTarget,
898
1502
  nextHeight,
899
1503
  parseHotkey,
1504
+ readText,
900
1505
  resolveSwipeIntent,
1506
+ shareOrCopy,
1507
+ shareTextFor,
1508
+ shouldResubscribe,
1509
+ startInstallPromptCapture,
901
1510
  unlockScroll,
1511
+ unregisterAll,
1512
+ urlBase64ToUint8Array,
902
1513
  useAutoGrowTextarea,
903
1514
  useCoarsePointer,
904
1515
  useCopyFeedback,
@@ -908,10 +1519,17 @@ function useAutoGrowTextarea(ref, value, { maxHeight } = {}) {
908
1519
  useEscape,
909
1520
  useFocusTrap,
910
1521
  useHotkey,
1522
+ useInstallPrompt,
1523
+ useIsStandalone,
1524
+ useKeyboardInset,
911
1525
  useMediaQuery,
1526
+ useOnlineStatus,
912
1527
  useOutsidePress,
1528
+ usePushSubscription,
913
1529
  useReducedMotion,
914
1530
  useScrollLock,
1531
+ useServiceWorker,
1532
+ useShareFeedback,
915
1533
  useStorageState,
916
1534
  useUnsavedChangesGuard,
917
1535
  useVisibilityPolling