@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,74 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useFile.d.ts
4
+ interface UseFileOptions {
5
+ /**
6
+ * Accepted file types (input accept attribute)
7
+ * Example: 'image/*', '.pdf'
8
+ */
9
+ accept?: string;
10
+ /**
11
+ * Whether the file input should be disabled
12
+ */
13
+ disabled?: boolean;
14
+ /**
15
+ * Allow selecting multiple files
16
+ */
17
+ multiple?: boolean;
18
+ }
19
+ interface UseFileReturn {
20
+ /**
21
+ * Currently selected files.
22
+ */
23
+ files: File[];
24
+ /**
25
+ * Whether at least one file is selected.
26
+ */
27
+ hasFiles: boolean;
28
+ /**
29
+ * Props to spread into an <input type="file" /> element.
30
+ */
31
+ inputProps: React.InputHTMLAttributes<HTMLInputElement>;
32
+ /**
33
+ * Clears the selected files.
34
+ */
35
+ reset: () => void;
36
+ /**
37
+ * Manually sets files (useful for drag & drop).
38
+ */
39
+ setFiles: (files: File[] | FileList) => void;
40
+ }
41
+ /**
42
+ * `useFile` is a React hook that manages file selection and access in an unopinionated and transport-agnostic way.
43
+ * This hook does not upload files, validate contents, or trigger side effects.
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * const file = useFile({ accept: 'image/*', multiple: true });
48
+ *
49
+ * return (
50
+ * <>
51
+ * <input {...file.inputProps} />
52
+ * {file.files.map((f) => (
53
+ * <p key={f.name}>{f.name}</p>
54
+ * ))}
55
+ * </>
56
+ * );
57
+ *
58
+ * `Use tipically with a button to trigger the file selection`
59
+ * <input {...file.inputProps} />;
60
+ * <button onClick={() => upload(file.files)}>
61
+ * Upload
62
+ * </button>
63
+ *
64
+ * `Use with Drag & Drop`
65
+ * const file = useFile({ multiple: true });
66
+ * const onDrop = (e: React.DragEvent) => {
67
+ * e.preventDefault();
68
+ * file.setFiles(e.dataTransfer.files);
69
+ * };
70
+ * ```
71
+ */
72
+ declare function useFile(options?: UseFileOptions): UseFileReturn;
73
+ //#endregion
74
+ export { useFile };
@@ -0,0 +1,74 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useFile.ts
4
+ /**
5
+ * `useFile` is a React hook that manages file selection and access in an unopinionated and transport-agnostic way.
6
+ * This hook does not upload files, validate contents, or trigger side effects.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const file = useFile({ accept: 'image/*', multiple: true });
11
+ *
12
+ * return (
13
+ * <>
14
+ * <input {...file.inputProps} />
15
+ * {file.files.map((f) => (
16
+ * <p key={f.name}>{f.name}</p>
17
+ * ))}
18
+ * </>
19
+ * );
20
+ *
21
+ * `Use tipically with a button to trigger the file selection`
22
+ * <input {...file.inputProps} />;
23
+ * <button onClick={() => upload(file.files)}>
24
+ * Upload
25
+ * </button>
26
+ *
27
+ * `Use with Drag & Drop`
28
+ * const file = useFile({ multiple: true });
29
+ * const onDrop = (e: React.DragEvent) => {
30
+ * e.preventDefault();
31
+ * file.setFiles(e.dataTransfer.files);
32
+ * };
33
+ * ```
34
+ */
35
+ function useFile(options = {}) {
36
+ const { accept, disabled, multiple = false } = options;
37
+ const [files, setInternalFiles] = React.useState([]);
38
+ const inputRef = React.useRef(null);
39
+ const setFiles = React.useCallback((input) => {
40
+ const next = Array.from(input);
41
+ setInternalFiles(multiple ? next : next.slice(0, 1));
42
+ }, [multiple]);
43
+ const reset = React.useCallback(() => {
44
+ setInternalFiles([]);
45
+ if (inputRef.current) inputRef.current.value = "";
46
+ }, []);
47
+ const onChange = React.useCallback((event) => {
48
+ if (!event.target.files) return;
49
+ setFiles(event.target.files);
50
+ }, [setFiles]);
51
+ const inputProps = React.useMemo(() => ({
52
+ accept,
53
+ disabled,
54
+ multiple,
55
+ onChange,
56
+ ref: inputRef,
57
+ type: "file"
58
+ }), [
59
+ accept,
60
+ multiple,
61
+ disabled,
62
+ onChange
63
+ ]);
64
+ return {
65
+ files,
66
+ hasFiles: files.length > 0,
67
+ inputProps,
68
+ reset,
69
+ setFiles
70
+ };
71
+ }
72
+
73
+ //#endregion
74
+ export { useFile };
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useFullScreen.d.ts
4
+ interface FullScreenReturn {
5
+ enter: () => Promise<void>;
6
+ exit: () => Promise<void>;
7
+ isFullscreen: boolean;
8
+ toggle: () => Promise<void>;
9
+ }
10
+ /**
11
+ * `useFullScreen` controls the browser Fullscreen API.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const { isFullscreen, enter, exit, toggle } = useFullscreen<HTMLDivElement>(ref);
16
+ * ```
17
+ */
18
+ declare function useFullscreen<T extends HTMLElement>(ref: React.RefObject<T | null>): FullScreenReturn;
19
+ //#endregion
20
+ export { useFullscreen };
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useFullScreen.ts
4
+ /**
5
+ * `useFullScreen` controls the browser Fullscreen API.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { isFullscreen, enter, exit, toggle } = useFullscreen<HTMLDivElement>(ref);
10
+ * ```
11
+ */
12
+ function useFullscreen(ref) {
13
+ const [isFullscreen, setIsFullscreen] = React.useState(false);
14
+ const enter = React.useCallback(async () => {
15
+ if (!ref.current) return;
16
+ if (document.fullscreenElement) return;
17
+ await ref.current.requestFullscreen();
18
+ }, []);
19
+ const exit = React.useCallback(async () => {
20
+ if (!document.fullscreenElement) return;
21
+ await document.exitFullscreen();
22
+ }, []);
23
+ const toggle = React.useCallback(async () => {
24
+ if (document.fullscreenElement) await exit();
25
+ else await enter();
26
+ }, [enter, exit]);
27
+ React.useEffect(() => {
28
+ const handleChange = () => {
29
+ setIsFullscreen(Boolean(document.fullscreenElement));
30
+ };
31
+ document.addEventListener("fullscreenchange", handleChange);
32
+ return () => document.removeEventListener("fullscreenchange", handleChange);
33
+ }, []);
34
+ return {
35
+ enter,
36
+ exit,
37
+ isFullscreen,
38
+ toggle
39
+ };
40
+ }
41
+
42
+ //#endregion
43
+ export { useFullscreen };
@@ -0,0 +1,47 @@
1
+ //#region src/useGeolocation.d.ts
2
+ type GeolocationPermissionState = PermissionState | 'unsupported';
3
+ interface UseGeolocationReturn {
4
+ /**
5
+ * Clears a watcher.
6
+ */
7
+ clearWatch: (watcherId: number) => void;
8
+ /**
9
+ * Latest geolocation error.
10
+ */
11
+ error: GeolocationPositionError | null;
12
+ /**
13
+ * Requests the current position once.
14
+ */
15
+ getCurrentPosition: (options?: PositionOptions) => void;
16
+ /**
17
+ * Indicates whether the Geolocation API is supported.
18
+ */
19
+ isSupported: boolean;
20
+ /**
21
+ * Current permission state for geolocation.
22
+ * - 'granted'
23
+ * - 'denied'
24
+ * - 'prompt'
25
+ * - 'unsupported' (Permissions API not available)
26
+ */
27
+ permissionState: GeolocationPermissionState;
28
+ /**
29
+ * Latest known position.
30
+ */
31
+ position: GeolocationPosition | null;
32
+ /**
33
+ * Starts watching position changes.
34
+ */
35
+ watchPosition: (options?: PositionOptions) => number | null;
36
+ }
37
+ /**
38
+ * `useGeolocation` is a React hook to safely access the Geolocation API in an unopinionated and SSR-safe manner.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * const { clearWatch, error, getCurrentPosition, isSupported, permissionState, position, watchPosition } = useGeolocation();
43
+ * ```
44
+ */
45
+ declare function useGeolocation(): UseGeolocationReturn;
46
+ //#endregion
47
+ export { useGeolocation };
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useGeolocation.ts
4
+ /**
5
+ * `useGeolocation` is a React hook to safely access the Geolocation API in an unopinionated and SSR-safe manner.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { clearWatch, error, getCurrentPosition, isSupported, permissionState, position, watchPosition } = useGeolocation();
10
+ * ```
11
+ */
12
+ function useGeolocation() {
13
+ const isClient = typeof window !== "undefined" && typeof navigator !== "undefined";
14
+ const isSupported = isClient && "geolocation" in navigator;
15
+ const [position, setPosition] = React.useState(null);
16
+ const [error, setError] = React.useState(null);
17
+ const [permissionState, setPermissionState] = React.useState(isClient && "permissions" in navigator ? "prompt" : "unsupported");
18
+ React.useEffect(() => {
19
+ if (!isClient || !("permissions" in navigator) || !isSupported) return;
20
+ let status = null;
21
+ navigator.permissions.query({ name: "geolocation" }).then((permissionStatus) => {
22
+ status = permissionStatus;
23
+ setPermissionState(permissionStatus.state);
24
+ permissionStatus.onchange = () => {
25
+ setPermissionState(permissionStatus.state);
26
+ };
27
+ }).catch(() => {
28
+ setPermissionState("unsupported");
29
+ });
30
+ return () => {
31
+ if (status) status.onchange = null;
32
+ };
33
+ }, [isClient, isSupported]);
34
+ const getCurrentPosition = React.useCallback((options) => {
35
+ if (!isSupported) return;
36
+ navigator.geolocation.getCurrentPosition((position$1) => {
37
+ setPosition(position$1);
38
+ setError(null);
39
+ }, (err) => {
40
+ setError(err);
41
+ setPosition(null);
42
+ }, options);
43
+ }, [isSupported]);
44
+ const watchPosition = React.useCallback((options) => {
45
+ if (!isSupported) return null;
46
+ return navigator.geolocation.watchPosition((pos) => {
47
+ setPosition(pos);
48
+ setError(null);
49
+ }, (err) => {
50
+ setError(err);
51
+ }, options);
52
+ }, [isSupported]);
53
+ return {
54
+ clearWatch: React.useCallback((watcherId) => {
55
+ if (!isSupported) return;
56
+ navigator.geolocation.clearWatch(watcherId);
57
+ }, [isSupported]),
58
+ error,
59
+ getCurrentPosition,
60
+ isSupported,
61
+ permissionState,
62
+ position,
63
+ watchPosition
64
+ };
65
+ }
66
+
67
+ //#endregion
68
+ export { useGeolocation };
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useHoverIntent.d.ts
4
+ interface UseHoverIntentOptions {
5
+ /**
6
+ * Delay (ms) before considering hover as intentional.
7
+ */
8
+ delay?: number;
9
+ /**
10
+ * Pixel movement tolerance before canceling intent.
11
+ */
12
+ tolerance?: number;
13
+ }
14
+ interface UseHoverIntentReturn {
15
+ /**
16
+ * Event handlers to spread on the target element.
17
+ */
18
+ handlers: {
19
+ onMouseEnter?: React.MouseEventHandler;
20
+ onMouseLeave?: React.MouseEventHandler;
21
+ onMouseMove?: React.MouseEventHandler;
22
+ };
23
+ /**
24
+ * Whether the hover is considered intentional.
25
+ */
26
+ isIntent: boolean;
27
+ }
28
+ /**
29
+ * `useHoverIntent` detects whether a mouse hover is intentional by observing pointer movement and time spent over an element.
30
+ * It is useful for preventing accidental hover interactions.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const hover = useHoverIntent({ delay: 150, tolerance: 8 });
35
+ *
36
+ * return (
37
+ * <div {...hover.handlers}>
38
+ * {hover.isIntent && <Tooltip />}
39
+ * </div>
40
+ * );
41
+ * ```
42
+ */
43
+ declare function useHoverIntent(options?: UseHoverIntentOptions): UseHoverIntentReturn;
44
+ //#endregion
45
+ export { useHoverIntent };
@@ -0,0 +1,81 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useHoverIntent.ts
4
+ /**
5
+ * `useHoverIntent` detects whether a mouse hover is intentional by observing pointer movement and time spent over an element.
6
+ * It is useful for preventing accidental hover interactions.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const hover = useHoverIntent({ delay: 150, tolerance: 8 });
11
+ *
12
+ * return (
13
+ * <div {...hover.handlers}>
14
+ * {hover.isIntent && <Tooltip />}
15
+ * </div>
16
+ * );
17
+ * ```
18
+ */
19
+ function useHoverIntent(options = {}) {
20
+ const { delay = 100, tolerance = 6 } = options;
21
+ const [isIntent, setIsIntent] = React.useState(false);
22
+ const timeoutRef = React.useRef(null);
23
+ const lastPointRef = React.useRef(null);
24
+ const clear = React.useCallback(() => {
25
+ if (timeoutRef.current !== null) {
26
+ window.clearTimeout(timeoutRef.current);
27
+ timeoutRef.current = null;
28
+ }
29
+ }, []);
30
+ const onMouseEnter = React.useCallback((event) => {
31
+ lastPointRef.current = {
32
+ x: event.clientX,
33
+ y: event.clientY
34
+ };
35
+ clear();
36
+ timeoutRef.current = window.setTimeout(() => {
37
+ setIsIntent(true);
38
+ }, delay);
39
+ }, [delay, clear]);
40
+ const onMouseMove = React.useCallback((event) => {
41
+ if (!lastPointRef.current) return;
42
+ const dx = Math.abs(event.clientX - lastPointRef.current.x);
43
+ const dy = Math.abs(event.clientY - lastPointRef.current.y);
44
+ if (dx > tolerance || dy > tolerance) {
45
+ clear();
46
+ setIsIntent(false);
47
+ timeoutRef.current = window.setTimeout(() => {
48
+ setIsIntent(true);
49
+ }, delay);
50
+ lastPointRef.current = {
51
+ x: event.clientX,
52
+ y: event.clientY
53
+ };
54
+ }
55
+ }, [
56
+ tolerance,
57
+ clear,
58
+ delay
59
+ ]);
60
+ const onMouseLeave = React.useCallback(() => {
61
+ clear();
62
+ lastPointRef.current = null;
63
+ setIsIntent(false);
64
+ }, [clear]);
65
+ React.useEffect(() => {
66
+ return () => {
67
+ clear();
68
+ };
69
+ }, [clear]);
70
+ return {
71
+ handlers: {
72
+ onMouseEnter,
73
+ onMouseLeave,
74
+ onMouseMove
75
+ },
76
+ isIntent
77
+ };
78
+ }
79
+
80
+ //#endregion
81
+ export { useHoverIntent };
@@ -0,0 +1,47 @@
1
+ //#region src/useIdle.d.ts
2
+ interface UseIdleOptions {
3
+ /**
4
+ * Events that reset the idle timer.
5
+ * @default ['mousemove', 'keydown', 'mousedown', 'touchstart', 'scroll']
6
+ */
7
+ events?: readonly (keyof WindowEventMap)[];
8
+ /**
9
+ * Initial idle state.
10
+ * Useful for SSR or background tabs.
11
+ * @default false
12
+ */
13
+ initialState?: boolean;
14
+ /**
15
+ * Time in milliseconds before the user is considered idle.
16
+ * @default 60000
17
+ */
18
+ timeout?: number;
19
+ }
20
+ interface UseIdleReturn {
21
+ /**
22
+ * Whether the user is currently idle.
23
+ */
24
+ isIdle: boolean;
25
+ /**
26
+ * Timestamp (ms) of the last detected user activity.
27
+ */
28
+ lastActiveAt: number;
29
+ }
30
+ /**
31
+ * `useIdle` is a React hook to detect user inactivity.
32
+ * It observes user interaction events and marks the user as idle after a specified period without activity.
33
+ * This hook does not perform side effects (logout, pause, etc.).
34
+ * It only exposes idle state so the consumer can decide what to do.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * const { isIdle } = useIdle({ timeout: 30000 });
39
+ *
40
+ * if (isIdle) {
41
+ * // pause expensive work
42
+ * }
43
+ * ```
44
+ */
45
+ declare function useIdle(options?: UseIdleOptions): UseIdleReturn;
46
+ //#endregion
47
+ export { useIdle };
@@ -0,0 +1,59 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useIdle.ts
4
+ /**
5
+ * `useIdle` is a React hook to detect user inactivity.
6
+ * It observes user interaction events and marks the user as idle after a specified period without activity.
7
+ * This hook does not perform side effects (logout, pause, etc.).
8
+ * It only exposes idle state so the consumer can decide what to do.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const { isIdle } = useIdle({ timeout: 30000 });
13
+ *
14
+ * if (isIdle) {
15
+ * // pause expensive work
16
+ * }
17
+ * ```
18
+ */
19
+ function useIdle(options = {}) {
20
+ const { events = [
21
+ "mousemove",
22
+ "keydown",
23
+ "mousedown",
24
+ "touchstart",
25
+ "scroll"
26
+ ], initialState = false, timeout = 6e4 } = options;
27
+ const isBrowser = typeof window !== "undefined";
28
+ const [isIdle, setIsIdle] = React.useState(initialState);
29
+ const lasActiveRef = React.useRef(Date.now());
30
+ const timeoutRef = React.useRef(null);
31
+ const resetTimer = React.useCallback(() => {
32
+ lasActiveRef.current = Date.now();
33
+ setIsIdle((prev) => prev ? false : prev);
34
+ if (timeoutRef.current !== null) window.clearTimeout(timeoutRef.current);
35
+ timeoutRef.current = window.setTimeout(() => {
36
+ setIsIdle(true);
37
+ }, timeout);
38
+ }, [timeout]);
39
+ React.useEffect(() => {
40
+ if (!isBrowser) return;
41
+ resetTimer();
42
+ for (const event of events) window.addEventListener(event, resetTimer, { passive: true });
43
+ return () => {
44
+ if (timeoutRef.current !== null) window.clearTimeout(timeoutRef.current);
45
+ for (const event of events) window.removeEventListener(event, resetTimer);
46
+ };
47
+ }, [
48
+ events,
49
+ isBrowser,
50
+ resetTimer
51
+ ]);
52
+ return {
53
+ isIdle,
54
+ lastActiveAt: lasActiveRef.current
55
+ };
56
+ }
57
+
58
+ //#endregion
59
+ export { useIdle };
@@ -0,0 +1,60 @@
1
+ //#region src/useIndexedDB.d.ts
2
+ interface UseIndexedDBOptions {
3
+ /**
4
+ * Database name
5
+ */
6
+ name: string;
7
+ /**
8
+ * Option upgrade callback
9
+ */
10
+ onUpgrade?: (db: IDBDatabase, oldVersion: number, newVersion: number | null) => void;
11
+ /**
12
+ * Database version
13
+ */
14
+ version: number;
15
+ }
16
+ interface UseIndexedDBTransactionOptions {
17
+ mode?: IDBTransactionMode;
18
+ }
19
+ interface UseIndexedDBTransactionReturn {
20
+ /**
21
+ * Closes the database connection
22
+ */
23
+ close: () => void;
24
+ /**
25
+ * Deletes the database
26
+ */
27
+ deleteDatabase: () => Promise<void>;
28
+ /**
29
+ * Opens the database connection
30
+ */
31
+ open: () => Promise<IDBDatabase>;
32
+ /**
33
+ * Runs a transaction and exposes the object store
34
+ */
35
+ withStore: <T>(storeName: string, fn: (store: IDBObjectStore) => Promise<T>, options?: UseIndexedDBTransactionOptions) => Promise<T>;
36
+ }
37
+ /**
38
+ * `useIndexedDB` is a React hook that provides unopinionated access to IndexedDB.
39
+ * This hook does not manage schemas, objet stores, or data shape.
40
+ * It only abstracts database lifecycle and transaction boilerplate.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const db = useIndexedDB({
45
+ * name: 'app-db',
46
+ * version: 1,
47
+ * onUpgrade(db) {
48
+ * db.createObjectStore('users', { keyPath: 'id' });
49
+ * },
50
+ * });
51
+ *
52
+ * await db.withStore('users', async (store) => {
53
+ * store.put({ id: '1', name: 'Sebas' });
54
+ * });
55
+ * ```
56
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
57
+ */
58
+ declare function useIndexedDB(options: UseIndexedDBOptions): UseIndexedDBTransactionReturn;
59
+ //#endregion
60
+ export { useIndexedDB };
@@ -0,0 +1,75 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useIndexedDB.ts
4
+ /**
5
+ * `useIndexedDB` is a React hook that provides unopinionated access to IndexedDB.
6
+ * This hook does not manage schemas, objet stores, or data shape.
7
+ * It only abstracts database lifecycle and transaction boilerplate.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const db = useIndexedDB({
12
+ * name: 'app-db',
13
+ * version: 1,
14
+ * onUpgrade(db) {
15
+ * db.createObjectStore('users', { keyPath: 'id' });
16
+ * },
17
+ * });
18
+ *
19
+ * await db.withStore('users', async (store) => {
20
+ * store.put({ id: '1', name: 'Sebas' });
21
+ * });
22
+ * ```
23
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
24
+ */
25
+ function useIndexedDB(options) {
26
+ const dbRef = React.useRef(null);
27
+ const isSupported = typeof window !== "undefined" && typeof window.indexedDB !== "undefined";
28
+ const open = React.useCallback(() => {
29
+ if (!isSupported) return Promise.reject(/* @__PURE__ */ new Error("IndexedDB not supported."));
30
+ if (dbRef.current) return Promise.resolve(dbRef.current);
31
+ return new Promise((resolve, reject) => {
32
+ const request = indexedDB.open(options.name, options.version);
33
+ request.onupgradeneeded = (event) => {
34
+ options.onUpgrade?.(request.result, event.oldVersion, event.newVersion);
35
+ };
36
+ request.onsuccess = () => {
37
+ dbRef.current = request.result;
38
+ resolve(dbRef.current);
39
+ };
40
+ request.onerror = () => {
41
+ reject(request.error);
42
+ };
43
+ });
44
+ }, [isSupported, options]);
45
+ const withStore = React.useCallback(async (storeName, fn, options$1) => {
46
+ const db = await open();
47
+ return new Promise((resolve, reject) => {
48
+ const tx = db.transaction(storeName, options$1?.mode ?? "readwrite");
49
+ fn(tx.objectStore(storeName)).then((result) => {
50
+ tx.oncomplete = () => resolve(result);
51
+ tx.onerror = () => reject(tx.error);
52
+ }).catch(reject);
53
+ });
54
+ }, [open]);
55
+ const close = React.useCallback(() => {
56
+ dbRef.current?.close();
57
+ dbRef.current = null;
58
+ }, []);
59
+ return {
60
+ close,
61
+ deleteDatabase: React.useCallback(() => {
62
+ close();
63
+ return new Promise((resolve, reject) => {
64
+ const request = indexedDB.deleteDatabase(options.name);
65
+ request.onsuccess = () => resolve();
66
+ request.onerror = () => reject(request.error);
67
+ });
68
+ }, [close, options.name]),
69
+ open,
70
+ withStore
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ export { useIndexedDB };