@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.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +55 -0
- package/dist/useAsyncState.d.ts +52 -0
- package/dist/useAsyncState.js +173 -0
- package/dist/useAudio.d.ts +26 -0
- package/dist/useAudio.js +64 -0
- package/dist/useAutoScroll.d.ts +47 -0
- package/dist/useAutoScroll.js +122 -0
- package/dist/useBarcode.d.ts +77 -0
- package/dist/useBarcode.js +140 -0
- package/dist/useBatteryStatus.d.ts +53 -0
- package/dist/useBatteryStatus.js +67 -0
- package/dist/useBodyScrollFreeze.d.ts +36 -0
- package/dist/useBodyScrollFreeze.js +74 -0
- package/dist/useCameraCapture.d.ts +76 -0
- package/dist/useCameraCapture.js +116 -0
- package/dist/useCookies.d.ts +42 -0
- package/dist/useCookies.js +61 -0
- package/dist/useCopyToClipboard.d.ts +22 -0
- package/dist/useCopyToClipboard.js +31 -0
- package/dist/useCountDown.d.ts +80 -0
- package/dist/useCountDown.js +106 -0
- package/dist/useDebouncedState.d.ts +47 -0
- package/dist/useDebouncedState.js +47 -0
- package/dist/useExternalNotifications.d.ts +36 -0
- package/dist/useExternalNotifications.js +100 -0
- package/dist/useFile.d.ts +74 -0
- package/dist/useFile.js +74 -0
- package/dist/useFullScreen.d.ts +20 -0
- package/dist/useFullScreen.js +43 -0
- package/dist/useGeolocation.d.ts +47 -0
- package/dist/useGeolocation.js +68 -0
- package/dist/useHoverIntent.d.ts +45 -0
- package/dist/useHoverIntent.js +81 -0
- package/dist/useIdle.d.ts +47 -0
- package/dist/useIdle.js +59 -0
- package/dist/useIndexedDB.d.ts +60 -0
- package/dist/useIndexedDB.js +75 -0
- package/dist/useIntersectionObserver.d.ts +45 -0
- package/dist/useIntersectionObserver.js +70 -0
- package/dist/useIntervalSafe.d.ts +72 -0
- package/dist/useIntervalSafe.js +85 -0
- package/dist/useIsClient.d.ts +12 -0
- package/dist/useIsClient.js +21 -0
- package/dist/useIsDesktop.d.ts +12 -0
- package/dist/useIsDesktop.js +23 -0
- package/dist/useIsFirstRender.d.ts +12 -0
- package/dist/useIsFirstRender.js +21 -0
- package/dist/useList.d.ts +19 -0
- package/dist/useList.js +44 -0
- package/dist/useLocalNotifications.d.ts +23 -0
- package/dist/useLocalNotifications.js +50 -0
- package/dist/useLocalStorage.d.ts +45 -0
- package/dist/useLocalStorage.js +71 -0
- package/dist/useNetworkInformation.d.ts +138 -0
- package/dist/useNetworkInformation.js +76 -0
- package/dist/useOnline.d.ts +17 -0
- package/dist/useOnline.js +29 -0
- package/dist/usePageVisibility.d.ts +32 -0
- package/dist/usePageVisibility.js +65 -0
- package/dist/usePermissions.d.ts +28 -0
- package/dist/usePermissions.js +70 -0
- package/dist/usePictureInPicture.d.ts +47 -0
- package/dist/usePictureInPicture.js +60 -0
- package/dist/usePopover.d.ts +54 -0
- package/dist/usePopover.js +67 -0
- package/dist/usePreferredLanguage.d.ts +55 -0
- package/dist/usePreferredLanguage.js +127 -0
- package/dist/usePreferredTheme.d.ts +67 -0
- package/dist/usePreferredTheme.js +133 -0
- package/dist/usePreviousDistinct.d.ts +12 -0
- package/dist/usePreviousDistinct.js +23 -0
- package/dist/useResettableState.d.ts +15 -0
- package/dist/useResettableState.js +25 -0
- package/dist/useScreenOrientation.d.ts +48 -0
- package/dist/useScreenOrientation.js +51 -0
- package/dist/useScreenSize.d.ts +16 -0
- package/dist/useScreenSize.js +34 -0
- package/dist/useScreenWakeLock.d.ts +37 -0
- package/dist/useScreenWakeLock.js +48 -0
- package/dist/useServerSentEvent.d.ts +57 -0
- package/dist/useServerSentEvent.js +78 -0
- package/dist/useShoppingCart.d.ts +54 -0
- package/dist/useShoppingCart.js +122 -0
- package/dist/useSmartVideo.d.ts +35 -0
- package/dist/useSmartVideo.js +76 -0
- package/dist/useSpeech.d.ts +74 -0
- package/dist/useSpeech.js +156 -0
- package/dist/useSummarizer.d.ts +92 -0
- package/dist/useSummarizer.js +83 -0
- package/dist/useTaskQueue.d.ts +25 -0
- package/dist/useTaskQueue.js +51 -0
- package/dist/useThrottledCallback.d.ts +32 -0
- package/dist/useThrottledCallback.js +42 -0
- package/dist/useTimeout.d.ts +58 -0
- package/dist/useTimeout.js +70 -0
- package/dist/useToggle.d.ts +30 -0
- package/dist/useToggle.js +23 -0
- package/dist/useTraceUpdates.d.ts +22 -0
- package/dist/useTraceUpdates.js +38 -0
- package/dist/useTranslator.d.ts +110 -0
- package/dist/useTranslator.js +119 -0
- package/dist/useUserActivation.d.ts +40 -0
- package/dist/useUserActivation.js +63 -0
- package/dist/useVibration.d.ts +55 -0
- package/dist/useVibration.js +50 -0
- package/dist/useWebsocket.d.ts +80 -0
- package/dist/useWebsocket.js +125 -0
- 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 };
|
package/dist/useFile.js
ADDED
|
@@ -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 };
|
package/dist/useIdle.js
ADDED
|
@@ -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 };
|