@vibehooks/react 0.0.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/index.d.ts +54 -0
  4. package/dist/index.js +55 -0
  5. package/dist/useAsyncState.d.ts +52 -0
  6. package/dist/useAsyncState.js +173 -0
  7. package/dist/useAudio.d.ts +26 -0
  8. package/dist/useAudio.js +64 -0
  9. package/dist/useAutoScroll.d.ts +47 -0
  10. package/dist/useAutoScroll.js +122 -0
  11. package/dist/useBarcode.d.ts +77 -0
  12. package/dist/useBarcode.js +140 -0
  13. package/dist/useBatteryStatus.d.ts +53 -0
  14. package/dist/useBatteryStatus.js +67 -0
  15. package/dist/useBodyScrollFreeze.d.ts +36 -0
  16. package/dist/useBodyScrollFreeze.js +74 -0
  17. package/dist/useCameraCapture.d.ts +76 -0
  18. package/dist/useCameraCapture.js +116 -0
  19. package/dist/useCookies.d.ts +42 -0
  20. package/dist/useCookies.js +61 -0
  21. package/dist/useCopyToClipboard.d.ts +22 -0
  22. package/dist/useCopyToClipboard.js +31 -0
  23. package/dist/useCountDown.d.ts +80 -0
  24. package/dist/useCountDown.js +106 -0
  25. package/dist/useDebouncedState.d.ts +47 -0
  26. package/dist/useDebouncedState.js +47 -0
  27. package/dist/useExternalNotifications.d.ts +36 -0
  28. package/dist/useExternalNotifications.js +100 -0
  29. package/dist/useFile.d.ts +74 -0
  30. package/dist/useFile.js +74 -0
  31. package/dist/useFullScreen.d.ts +20 -0
  32. package/dist/useFullScreen.js +43 -0
  33. package/dist/useGeolocation.d.ts +47 -0
  34. package/dist/useGeolocation.js +68 -0
  35. package/dist/useHoverIntent.d.ts +45 -0
  36. package/dist/useHoverIntent.js +81 -0
  37. package/dist/useIdle.d.ts +47 -0
  38. package/dist/useIdle.js +59 -0
  39. package/dist/useIndexedDB.d.ts +60 -0
  40. package/dist/useIndexedDB.js +75 -0
  41. package/dist/useIntersectionObserver.d.ts +45 -0
  42. package/dist/useIntersectionObserver.js +70 -0
  43. package/dist/useIntervalSafe.d.ts +72 -0
  44. package/dist/useIntervalSafe.js +85 -0
  45. package/dist/useIsClient.d.ts +12 -0
  46. package/dist/useIsClient.js +21 -0
  47. package/dist/useIsDesktop.d.ts +12 -0
  48. package/dist/useIsDesktop.js +23 -0
  49. package/dist/useIsFirstRender.d.ts +12 -0
  50. package/dist/useIsFirstRender.js +21 -0
  51. package/dist/useList.d.ts +19 -0
  52. package/dist/useList.js +44 -0
  53. package/dist/useLocalNotifications.d.ts +23 -0
  54. package/dist/useLocalNotifications.js +50 -0
  55. package/dist/useLocalStorage.d.ts +45 -0
  56. package/dist/useLocalStorage.js +71 -0
  57. package/dist/useNetworkInformation.d.ts +138 -0
  58. package/dist/useNetworkInformation.js +76 -0
  59. package/dist/useOnline.d.ts +17 -0
  60. package/dist/useOnline.js +29 -0
  61. package/dist/usePageVisibility.d.ts +32 -0
  62. package/dist/usePageVisibility.js +65 -0
  63. package/dist/usePermissions.d.ts +28 -0
  64. package/dist/usePermissions.js +70 -0
  65. package/dist/usePictureInPicture.d.ts +47 -0
  66. package/dist/usePictureInPicture.js +60 -0
  67. package/dist/usePopover.d.ts +54 -0
  68. package/dist/usePopover.js +67 -0
  69. package/dist/usePreferredLanguage.d.ts +55 -0
  70. package/dist/usePreferredLanguage.js +127 -0
  71. package/dist/usePreferredTheme.d.ts +67 -0
  72. package/dist/usePreferredTheme.js +133 -0
  73. package/dist/usePreviousDistinct.d.ts +12 -0
  74. package/dist/usePreviousDistinct.js +23 -0
  75. package/dist/useResettableState.d.ts +15 -0
  76. package/dist/useResettableState.js +25 -0
  77. package/dist/useScreenOrientation.d.ts +48 -0
  78. package/dist/useScreenOrientation.js +51 -0
  79. package/dist/useScreenSize.d.ts +16 -0
  80. package/dist/useScreenSize.js +34 -0
  81. package/dist/useScreenWakeLock.d.ts +37 -0
  82. package/dist/useScreenWakeLock.js +48 -0
  83. package/dist/useServerSentEvent.d.ts +57 -0
  84. package/dist/useServerSentEvent.js +78 -0
  85. package/dist/useShoppingCart.d.ts +54 -0
  86. package/dist/useShoppingCart.js +122 -0
  87. package/dist/useSmartVideo.d.ts +35 -0
  88. package/dist/useSmartVideo.js +76 -0
  89. package/dist/useSpeech.d.ts +74 -0
  90. package/dist/useSpeech.js +156 -0
  91. package/dist/useSummarizer.d.ts +92 -0
  92. package/dist/useSummarizer.js +83 -0
  93. package/dist/useTaskQueue.d.ts +25 -0
  94. package/dist/useTaskQueue.js +51 -0
  95. package/dist/useThrottledCallback.d.ts +32 -0
  96. package/dist/useThrottledCallback.js +42 -0
  97. package/dist/useTimeout.d.ts +58 -0
  98. package/dist/useTimeout.js +70 -0
  99. package/dist/useToggle.d.ts +30 -0
  100. package/dist/useToggle.js +23 -0
  101. package/dist/useTraceUpdates.d.ts +22 -0
  102. package/dist/useTraceUpdates.js +38 -0
  103. package/dist/useTranslator.d.ts +110 -0
  104. package/dist/useTranslator.js +119 -0
  105. package/dist/useUserActivation.d.ts +40 -0
  106. package/dist/useUserActivation.js +63 -0
  107. package/dist/useVibration.d.ts +55 -0
  108. package/dist/useVibration.js +50 -0
  109. package/dist/useWebsocket.d.ts +80 -0
  110. package/dist/useWebsocket.js +125 -0
  111. package/package.json +70 -0
@@ -0,0 +1,116 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useCameraCapture.ts
4
+ function createExternalStore(getSnapshot) {
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ return {
7
+ getSnapshot,
8
+ notify() {
9
+ listeners.forEach((l) => l());
10
+ },
11
+ subscribe(listener) {
12
+ listeners.add(listener);
13
+ return () => listeners.delete(listener);
14
+ }
15
+ };
16
+ }
17
+ /**
18
+ * Unopinionated, SSR-safe React hook for capturing still photos from the user's camera using getUserMedia and Canvas.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const { videoRef, requestPermission, capture } = useCameraCapture();
23
+ * ```
24
+ */
25
+ function useCameraCapture(options = {}) {
26
+ const { onCapture, width = 320 } = options;
27
+ const output = {
28
+ quality: options.format?.quality ?? .8,
29
+ type: options.format?.type ?? "image/png"
30
+ };
31
+ const videoRef = React.useRef(null);
32
+ const canvasRef = React.useRef(null);
33
+ const imageRef = React.useRef(null);
34
+ const streamRef = React.useRef(null);
35
+ const streamingRef = React.useRef(false);
36
+ const heightRef = React.useRef(0);
37
+ const isBrowser = typeof window !== "undefined";
38
+ const permissionStoreRef = React.useRef(createExternalStore(() => !!streamRef.current));
39
+ const streamingStoreRef = React.useRef(createExternalStore(() => streamingRef.current));
40
+ const requestPermission = React.useCallback(async () => {
41
+ if (!isBrowser) return false;
42
+ if (!navigator.mediaDevices?.getUserMedia) return false;
43
+ try {
44
+ const stream = await navigator.mediaDevices.getUserMedia({
45
+ audio: false,
46
+ video: true
47
+ });
48
+ streamRef.current = stream;
49
+ permissionStoreRef.current.notify();
50
+ const video = videoRef.current;
51
+ if (!video) return false;
52
+ video.srcObject = stream;
53
+ await video.play();
54
+ if (!streamingRef.current) {
55
+ const { videoHeight, videoWidth } = video;
56
+ heightRef.current = videoHeight / (videoWidth / width);
57
+ video.width = width;
58
+ video.height = heightRef.current;
59
+ const canvas = canvasRef.current;
60
+ if (canvas) {
61
+ canvas.width = width;
62
+ canvas.height = heightRef.current;
63
+ }
64
+ streamingRef.current = true;
65
+ streamingStoreRef.current.notify();
66
+ }
67
+ return true;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }, [isBrowser, width]);
72
+ const capture = React.useCallback(() => {
73
+ const video = videoRef.current;
74
+ const canvas = canvasRef.current;
75
+ const image = imageRef.current;
76
+ if (!video || !canvas || !streamingRef.current) return null;
77
+ const context = canvas.getContext("2d");
78
+ if (!context) return null;
79
+ canvas.width = width;
80
+ canvas.height = heightRef.current;
81
+ context.drawImage(video, 0, 0, width, heightRef.current);
82
+ const dataUrl = canvas.toDataURL(output.type, output.quality);
83
+ image?.setAttribute("src", dataUrl);
84
+ if (onCapture) canvas.toBlob((blob) => {
85
+ if (blob) onCapture(dataUrl, blob);
86
+ }, output.type, output.quality);
87
+ return dataUrl;
88
+ }, [
89
+ onCapture,
90
+ width,
91
+ output.quality,
92
+ output.type
93
+ ]);
94
+ const stop = React.useCallback(() => {
95
+ streamRef.current?.getTracks().forEach((track) => track.stop());
96
+ streamRef.current = null;
97
+ streamingRef.current = false;
98
+ permissionStoreRef.current.notify();
99
+ streamingStoreRef.current.notify();
100
+ }, []);
101
+ const usePermission = () => React.useSyncExternalStore(permissionStoreRef.current.subscribe, permissionStoreRef.current.getSnapshot, () => false);
102
+ const useStreaming = () => React.useSyncExternalStore(streamingStoreRef.current.subscribe, streamingStoreRef.current.getSnapshot, () => false);
103
+ return {
104
+ canvasRef,
105
+ capture,
106
+ imageRef,
107
+ requestPermission,
108
+ stop,
109
+ usePermission,
110
+ useStreaming,
111
+ videoRef
112
+ };
113
+ }
114
+
115
+ //#endregion
116
+ export { useCameraCapture };
@@ -0,0 +1,42 @@
1
+ //#region src/useCookies.d.ts
2
+ interface UseCookieOptions {
3
+ domain?: string;
4
+ expires?: Date;
5
+ maxAge?: number;
6
+ path?: string;
7
+ sameSite?: 'strict' | 'lax' | 'none';
8
+ secure?: boolean;
9
+ }
10
+ interface UseCookieReturn {
11
+ /**
12
+ * Reads a cookie value.
13
+ */
14
+ get: (name: string) => string | null;
15
+ /**
16
+ * Reads all cookies as a key-value map.
17
+ */
18
+ getAll: () => Record<string, string>;
19
+ /**
20
+ * Removes a cookie.
21
+ */
22
+ remove: (name: string, options?: UseCookieOptions) => void;
23
+ /**
24
+ * Sets a cookie value.
25
+ */
26
+ set: (name: string, value: string, options?: UseCookieOptions) => void;
27
+ }
28
+ /**
29
+ * `useCookies` is a React hook that provides unopinionated access to document cookies.
30
+ * This hook does not sync cookies to React state and does not perform encoding beyond basic string handling.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const cookies = useCookies();
35
+ * cookies.set('token', 'abc', { secure: true });
36
+ * const token = cookies.get('token');
37
+ * ```
38
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API
39
+ */
40
+ declare function useCookies(): UseCookieReturn;
41
+ //#endregion
42
+ export { useCookies };
@@ -0,0 +1,61 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useCookies.ts
4
+ /**
5
+ * `useCookies` is a React hook that provides unopinionated access to document cookies.
6
+ * This hook does not sync cookies to React state and does not perform encoding beyond basic string handling.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const cookies = useCookies();
11
+ * cookies.set('token', 'abc', { secure: true });
12
+ * const token = cookies.get('token');
13
+ * ```
14
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API
15
+ */
16
+ function useCookies() {
17
+ const isSupported = typeof document !== "undefined" && typeof document.cookie !== "undefined";
18
+ const get = React.useCallback((name) => {
19
+ if (!isSupported) return null;
20
+ const cookies = document.cookie.split("; ");
21
+ for (const cookie of cookies) {
22
+ const [key, ...rest] = cookie.split("=");
23
+ if (key === name) return decodeURIComponent(rest.join("="));
24
+ }
25
+ return null;
26
+ }, [isSupported]);
27
+ const set = React.useCallback((name, value, options = {}) => {
28
+ if (!isSupported) return;
29
+ let cookie = `${name}=${encodeURIComponent(value)}`;
30
+ if (options.maxAge !== void 0) cookie += `; max-age=${options.maxAge}`;
31
+ if (options.expires !== void 0) cookie += `; expires=${options.expires.toUTCString()}`;
32
+ if (options.path) cookie += `; path=${options.path}`;
33
+ if (options.domain) cookie += `; domain=${options.domain}`;
34
+ if (options.secure) cookie += "; secure";
35
+ if (options.sameSite) cookie += `; samesite=${options.sameSite}`;
36
+ document.cookie = cookie;
37
+ }, [isSupported]);
38
+ const remove = React.useCallback((name, options = {}) => {
39
+ set(name, "", {
40
+ ...options,
41
+ maxAge: 0
42
+ });
43
+ }, [set]);
44
+ return {
45
+ get,
46
+ getAll: React.useCallback(() => {
47
+ if (!isSupported) return {};
48
+ return document.cookie.split("; ").filter(Boolean).reduce((acc, cookie) => {
49
+ const [key, ...rest] = cookie.split("=");
50
+ if (!key) return acc;
51
+ acc[key] = decodeURIComponent(rest.join("="));
52
+ return acc;
53
+ }, {});
54
+ }, [isSupported]),
55
+ remove,
56
+ set
57
+ };
58
+ }
59
+
60
+ //#endregion
61
+ export { useCookies };
@@ -0,0 +1,22 @@
1
+ //#region src/useCopyToClipboard.d.ts
2
+ interface CopyToClipboardReturn {
3
+ copyToClipboard: (text: string) => Promise<void>;
4
+ textCopied: string | null;
5
+ }
6
+ /**
7
+ * `useCopyToClipboard` provides a safe way to copy text to the clipboard
8
+ * using the Clipboard API.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const { textCopied, copyToClipboard } = useCopyToClipboard();
13
+ *
14
+ * <button onClick={() => copyToClipboard('Hello!')}>
15
+ * Copy
16
+ * </button>
17
+ * ```
18
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
19
+ */
20
+ declare function useCopyToClipboard(): CopyToClipboardReturn;
21
+ //#endregion
22
+ export { useCopyToClipboard };
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useCopyToClipboard.ts
4
+ /**
5
+ * `useCopyToClipboard` provides a safe way to copy text to the clipboard
6
+ * using the Clipboard API.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const { textCopied, copyToClipboard } = useCopyToClipboard();
11
+ *
12
+ * <button onClick={() => copyToClipboard('Hello!')}>
13
+ * Copy
14
+ * </button>
15
+ * ```
16
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
17
+ */
18
+ function useCopyToClipboard() {
19
+ const [textCopied, setTextCopied] = React.useState(null);
20
+ return {
21
+ copyToClipboard: React.useCallback(async (text) => {
22
+ if (!navigator?.clipboard?.writeText) throw new Error("Clipboard API not available.");
23
+ await navigator.clipboard.writeText(text);
24
+ setTextCopied(text);
25
+ }, []),
26
+ textCopied
27
+ };
28
+ }
29
+
30
+ //#endregion
31
+ export { useCopyToClipboard };
@@ -0,0 +1,80 @@
1
+ //#region src/useCountDown.d.ts
2
+ type CountDownStatus = 'start' | 'running' | 'paused' | 'completed';
3
+ interface CountDownOptions {
4
+ /**
5
+ * The interval en milliseconds between each tick.
6
+ */
7
+ interval?: number;
8
+ /**
9
+ * A callback to be called when the countdown ends.
10
+ */
11
+ onComplete?: () => void;
12
+ /**
13
+ * A callback to be called on each tick.
14
+ */
15
+ onTick?: (time: number) => void;
16
+ }
17
+ interface CountDown {
18
+ /**
19
+ * The end time of the countdown in milliseconds.
20
+ */
21
+ endTime: number;
22
+ /**
23
+ *
24
+ */
25
+ options?: CountDownOptions;
26
+ /**
27
+ * Whether to start the countdown on mount. Default: false.
28
+ */
29
+ startOnMount?: boolean;
30
+ }
31
+ interface CountDownControlls {
32
+ /**
33
+ * Increments the countdown by a given amount of time in milliseconds.
34
+ */
35
+ increment: (ms?: number) => void;
36
+ /**
37
+ * Pauses the countdown.
38
+ */
39
+ pause: () => void;
40
+ /**
41
+ * Resets the countdown.
42
+ */
43
+ reset: () => void;
44
+ /**
45
+ * Resumes the countdown
46
+ */
47
+ resume: () => void;
48
+ /**
49
+ * Starts the countdown.
50
+ */
51
+ start: () => void;
52
+ }
53
+ interface CountDownReturn {
54
+ /**
55
+ * Stable references to control functions.
56
+ */
57
+ controls: CountDownControlls;
58
+ /**
59
+ * Remaining time in milliseconds.
60
+ * Will be `0` when the countdown has completed.
61
+ */
62
+ count: number | null;
63
+ /**
64
+ * Indicates the status of the countdown.
65
+ */
66
+ status: CountDownStatus;
67
+ }
68
+ /**
69
+ * `useCountDown` is a controllable countdown hook based on an absolute end timestamp.
70
+ * It uses a reference timestamp approach to prevent time drift commonly association with simple `setInterval` forcing.
71
+ *
72
+ * @see https://developer.mozilla.org/es/docs/Web/API/Window/setInterval
73
+ */
74
+ declare function useCountDown({
75
+ endTime,
76
+ options,
77
+ startOnMount
78
+ }: CountDown): CountDownReturn;
79
+ //#endregion
80
+ export { useCountDown };
@@ -0,0 +1,106 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useCountDown.ts
4
+ /**
5
+ * `useCountDown` is a controllable countdown hook based on an absolute end timestamp.
6
+ * It uses a reference timestamp approach to prevent time drift commonly association with simple `setInterval` forcing.
7
+ *
8
+ * @see https://developer.mozilla.org/es/docs/Web/API/Window/setInterval
9
+ */
10
+ function useCountDown({ endTime, options, startOnMount = false }) {
11
+ const intervalValue = options?.interval ?? 1e3;
12
+ const initialDurationRef = React.useRef(endTime - Date.now());
13
+ const endTimeRef = React.useRef(startOnMount ? Date.now() + initialDurationRef.current : 0);
14
+ const completedRef = React.useRef(false);
15
+ const remainingAtPauseRef = React.useRef(initialDurationRef.current);
16
+ const intervalIdRef = React.useRef(null);
17
+ const optionsRef = React.useRef(options);
18
+ React.useEffect(() => {
19
+ optionsRef.current = options;
20
+ }, [options]);
21
+ const [count, setCount] = React.useState(initialDurationRef.current);
22
+ const [status, setStatus] = React.useState(startOnMount ? "running" : "start");
23
+ const clearTimer = React.useCallback(() => {
24
+ if (intervalIdRef.current !== null) {
25
+ clearInterval(intervalIdRef.current);
26
+ intervalIdRef.current = null;
27
+ }
28
+ }, []);
29
+ const tick = React.useCallback(() => {
30
+ const remaining = Math.max(endTimeRef.current - Date.now(), 0);
31
+ optionsRef.current?.onTick?.(remaining);
32
+ setCount(remaining);
33
+ if (remaining <= 0 && !completedRef.current) {
34
+ completedRef.current = true;
35
+ clearTimer();
36
+ setStatus("completed");
37
+ optionsRef.current?.onComplete?.();
38
+ }
39
+ }, [clearTimer]);
40
+ const pause = React.useCallback(() => {
41
+ if (status !== "running") return;
42
+ clearTimer();
43
+ remainingAtPauseRef.current = Math.max(endTimeRef.current - Date.now(), 0);
44
+ setStatus("paused");
45
+ }, [status, clearTimer]);
46
+ const resume = React.useCallback(() => {
47
+ if (status !== "paused") return;
48
+ endTimeRef.current = Date.now() + remainingAtPauseRef.current;
49
+ setStatus("running");
50
+ }, [status]);
51
+ const reset = React.useCallback(() => {
52
+ clearTimer();
53
+ completedRef.current = false;
54
+ remainingAtPauseRef.current = initialDurationRef.current;
55
+ setCount(initialDurationRef.current);
56
+ setStatus("start");
57
+ }, [clearTimer]);
58
+ const increment = React.useCallback((ms) => {
59
+ if (!ms || ms <= 0 || completedRef.current) return;
60
+ endTimeRef.current += ms;
61
+ if (status === "paused") {
62
+ remainingAtPauseRef.current += ms;
63
+ setCount(remainingAtPauseRef.current);
64
+ }
65
+ }, [status]);
66
+ const start = React.useCallback(() => {
67
+ if (status !== "start") return;
68
+ endTimeRef.current = Date.now() + remainingAtPauseRef.current;
69
+ setStatus("running");
70
+ }, [status]);
71
+ React.useEffect(() => {
72
+ if (status !== "running") {
73
+ clearTimer();
74
+ return;
75
+ }
76
+ completedRef.current = false;
77
+ tick();
78
+ intervalIdRef.current = setInterval(tick, intervalValue);
79
+ return clearTimer;
80
+ }, [
81
+ status,
82
+ intervalValue,
83
+ tick,
84
+ clearTimer
85
+ ]);
86
+ return {
87
+ controls: React.useMemo(() => ({
88
+ increment,
89
+ pause,
90
+ reset,
91
+ resume,
92
+ start
93
+ }), [
94
+ start,
95
+ pause,
96
+ resume,
97
+ reset,
98
+ increment
99
+ ]),
100
+ count,
101
+ status
102
+ };
103
+ }
104
+
105
+ //#endregion
106
+ export { useCountDown };
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useDebouncedState.d.ts
4
+ interface UseDebouncedStateOptions {
5
+ /**
6
+ * Delay in milliseconds before the debounced value is updated.
7
+ */
8
+ delay: number;
9
+ }
10
+ interface UseDebouncedStateReturn<T> {
11
+ /**
12
+ * The debounced value, updated after the specified delay.
13
+ */
14
+ debouncedValue: T;
15
+ /**
16
+ * State setter for the inmediate value.
17
+ */
18
+ setValue: React.Dispatch<React.SetStateAction<T>>;
19
+ /**
20
+ * The inmediate (non-debounced) value.
21
+ */
22
+ value: T;
23
+ }
24
+ /**
25
+ * `useDebouncedState` is a React hook that manages a state value and exposes a debounced version of it.
26
+ * The debounced value is updated only after the specified delay has elapsed since the last change to the inmediate value.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * const { value, debouncedValue, setValue } = useDebouncedState('', {
31
+ * delay: 400,
32
+ * });
33
+ * React.useEffect(() => {
34
+ * if (!debouncedValue) return;
35
+ * search(debouncedValue);
36
+ * }, [debouncedValue]);
37
+ * return (
38
+ * <input
39
+ * value={value}
40
+ * onChange={(e) => setValue(e.target.value)}
41
+ * />
42
+ * );
43
+ * ```
44
+ */
45
+ declare function useDebouncedState<T>(initialValue: T, options: UseDebouncedStateOptions): UseDebouncedStateReturn<T>;
46
+ //#endregion
47
+ export { useDebouncedState };
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useDebouncedState.ts
4
+ /**
5
+ * `useDebouncedState` is a React hook that manages a state value and exposes a debounced version of it.
6
+ * The debounced value is updated only after the specified delay has elapsed since the last change to the inmediate value.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const { value, debouncedValue, setValue } = useDebouncedState('', {
11
+ * delay: 400,
12
+ * });
13
+ * React.useEffect(() => {
14
+ * if (!debouncedValue) return;
15
+ * search(debouncedValue);
16
+ * }, [debouncedValue]);
17
+ * return (
18
+ * <input
19
+ * value={value}
20
+ * onChange={(e) => setValue(e.target.value)}
21
+ * />
22
+ * );
23
+ * ```
24
+ */
25
+ function useDebouncedState(initialValue, options) {
26
+ const { delay } = options;
27
+ const [value, setValue] = React.useState(initialValue);
28
+ const [debouncedValue, setDebouncedValue] = React.useState(initialValue);
29
+ const timeoutRef = React.useRef(null);
30
+ React.useEffect(() => {
31
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
32
+ timeoutRef.current = setTimeout(() => {
33
+ setDebouncedValue(value);
34
+ }, delay);
35
+ return () => {
36
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
37
+ };
38
+ }, [value, delay]);
39
+ return {
40
+ debouncedValue,
41
+ setValue,
42
+ value
43
+ };
44
+ }
45
+
46
+ //#endregion
47
+ export { useDebouncedState };
@@ -0,0 +1,36 @@
1
+ //#region src/useExternalNotifications.d.ts
2
+ interface NotificationPayload extends NotificationOptions {
3
+ createdAt: number;
4
+ id?: string;
5
+ title: string;
6
+ }
7
+ interface UseExternalNotificationReturn {
8
+ isSupported: boolean;
9
+ notifications: NotificationPayload[] | null;
10
+ notify: (notification: Omit<NotificationPayload, 'createdAt'>) => void;
11
+ permission: NotificationPermission;
12
+ requestPermission: () => Promise<NotificationPermission>;
13
+ }
14
+ /**
15
+ * `useExternalNotifications` is React hook for consuming and emitting external notifications using a global store.
16
+ * It is thinking for events outside the normal cycle of React:
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * function NotificationCenter() {
21
+ * const { notifications } = useExternalNotification();
22
+ *
23
+ * return (
24
+ * <ul>
25
+ * {notifications.map(n => (
26
+ * <li key={n.id}>{n.title}</li>
27
+ * ))}
28
+ * </ul>
29
+ * );
30
+ * }
31
+ * ```
32
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Notification
33
+ */
34
+ declare function useExternalNotifications(): UseExternalNotificationReturn;
35
+ //#endregion
36
+ export { useExternalNotifications };
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useExternalNotifications.ts
4
+ let notifications = [];
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ let isListening = false;
7
+ function emit() {
8
+ listeners.forEach((listener) => listener());
9
+ }
10
+ function onStorageEvent(event) {
11
+ if (event.key !== "notifications") return;
12
+ try {
13
+ const next = JSON.parse(event.newValue ?? "[]");
14
+ if (Object.is(next, notifications)) return;
15
+ notifications = next;
16
+ emit();
17
+ } catch {}
18
+ }
19
+ function suscribe(listener) {
20
+ listeners.add(listener);
21
+ if (!isListening && typeof window !== "undefined") {
22
+ window.addEventListener("storage", onStorageEvent);
23
+ isListening = true;
24
+ }
25
+ return () => {
26
+ listeners.delete(listener);
27
+ if (listeners.size === 0 && isListening) {
28
+ window.removeEventListener("storage", onStorageEvent);
29
+ isListening = false;
30
+ }
31
+ };
32
+ }
33
+ function getSnapshot() {
34
+ return notifications;
35
+ }
36
+ const notificationStore = {
37
+ getSnapshot,
38
+ push(notification) {
39
+ notifications = [...notifications, notification];
40
+ localStorage.setItem("notifications", JSON.stringify(notifications));
41
+ emit();
42
+ },
43
+ suscribe
44
+ };
45
+ /**
46
+ * `useExternalNotifications` is React hook for consuming and emitting external notifications using a global store.
47
+ * It is thinking for events outside the normal cycle of React:
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * function NotificationCenter() {
52
+ * const { notifications } = useExternalNotification();
53
+ *
54
+ * return (
55
+ * <ul>
56
+ * {notifications.map(n => (
57
+ * <li key={n.id}>{n.title}</li>
58
+ * ))}
59
+ * </ul>
60
+ * );
61
+ * }
62
+ * ```
63
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Notification
64
+ */
65
+ function useExternalNotifications() {
66
+ const notifications$1 = React.useSyncExternalStore(notificationStore.suscribe, notificationStore.getSnapshot, () => null);
67
+ const isSupported = typeof window !== "undefined" && "Notification" in window;
68
+ const permission = isSupported ? Notification.permission : "denied";
69
+ const requestPermission = React.useCallback(async () => {
70
+ if (!isSupported) return "denied";
71
+ return await Notification.requestPermission();
72
+ }, [isSupported]);
73
+ return {
74
+ isSupported,
75
+ notifications: notifications$1,
76
+ notify: React.useCallback((notification) => {
77
+ const payload = {
78
+ ...notification,
79
+ createdAt: Date.now()
80
+ };
81
+ notificationStore.push(payload);
82
+ if (!isSupported || permission !== "granted") return;
83
+ const options = {
84
+ ...notification.badge !== void 0 && { badge: notification.badge },
85
+ ...notification.body !== void 0 && { body: notification.body },
86
+ ...notification.data !== void 0 && { data: notification.data },
87
+ ...notification.dir !== void 0 && { dir: notification.dir },
88
+ ...notification.icon !== void 0 && { icon: notification.icon },
89
+ ...notification.lang !== void 0 && { lang: notification.lang },
90
+ ...notification.requireInteraction !== void 0 && { requireInteraction: notification.requireInteraction }
91
+ };
92
+ new Notification(payload.title, options);
93
+ }, [isSupported, permission]),
94
+ permission,
95
+ requestPermission
96
+ };
97
+ }
98
+
99
+ //#endregion
100
+ export { useExternalNotifications };