@develemit/web-hooks 0.2.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 (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +145 -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-JG3GPTMY.js +101 -0
  10. package/dist/chunk-TNEJ34T5.js +86 -0
  11. package/dist/chunk-UJ366T3Y.js +146 -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 +609 -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 +201 -0
  42. package/dist/push.d.cts +37 -0
  43. package/dist/push.d.ts +37 -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 +128 -0
  50. package/dist/service-worker.d.cts +23 -0
  51. package/dist/service-worker.d.ts +23 -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,566 @@ 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 }, { 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
+ }
873
1437
  // Annotate the CommonJS export names for ESM import in node:
874
1438
  0 && (module.exports = {
875
1439
  BACK_ZONE_PX,
876
1440
  CLOSE_DISTANCE_PX,
877
1441
  COARSE_POINTER_QUERY,
1442
+ DEFAULT_IGNORE_SELECTOR,
878
1443
  FOCUSABLE_SELECTOR,
879
1444
  FORWARD_ZONE_PX,
880
1445
  MAX_SWIPE_DURATION_MS,
881
1446
  OPEN_DISTANCE_PX,
882
1447
  REDUCED_MOTION_QUERY,
883
1448
  VERTICAL_CANCEL_PX,
1449
+ canShareFiles,
1450
+ computeKeyboardInset,
884
1451
  copyText,
885
1452
  createDebouncer,
1453
+ deriveUpdateState,
886
1454
  detectApple,
1455
+ detectPlatform,
887
1456
  focusableWithin,
1457
+ getPushSupport,
888
1458
  handleBeforeUnload,
1459
+ isEditableElement,
889
1460
  isEditableTarget,
1461
+ isInIgnoredRegion,
890
1462
  isInOpenZone,
891
1463
  isInsideHorizontalScroller,
1464
+ isIos,
1465
+ isIosSafari,
892
1466
  isOutside,
893
1467
  isPrimaryPress,
1468
+ isShareCancel,
1469
+ isStandalone,
894
1470
  lockScroll,
895
1471
  matchHotkey,
896
1472
  nextDelay,
897
1473
  nextFocusTarget,
898
1474
  nextHeight,
899
1475
  parseHotkey,
1476
+ readText,
900
1477
  resolveSwipeIntent,
1478
+ shareOrCopy,
1479
+ shareTextFor,
1480
+ shouldResubscribe,
1481
+ startInstallPromptCapture,
901
1482
  unlockScroll,
1483
+ unregisterAll,
1484
+ urlBase64ToUint8Array,
902
1485
  useAutoGrowTextarea,
903
1486
  useCoarsePointer,
904
1487
  useCopyFeedback,
@@ -908,10 +1491,17 @@ function useAutoGrowTextarea(ref, value, { maxHeight } = {}) {
908
1491
  useEscape,
909
1492
  useFocusTrap,
910
1493
  useHotkey,
1494
+ useInstallPrompt,
1495
+ useIsStandalone,
1496
+ useKeyboardInset,
911
1497
  useMediaQuery,
1498
+ useOnlineStatus,
912
1499
  useOutsidePress,
1500
+ usePushSubscription,
913
1501
  useReducedMotion,
914
1502
  useScrollLock,
1503
+ useServiceWorker,
1504
+ useShareFeedback,
915
1505
  useStorageState,
916
1506
  useUnsavedChangesGuard,
917
1507
  useVisibilityPolling