@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,122 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useAutoScroll.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useAutoScroll` is a custom hook for automatically scrolling to the bottom of a container element.
|
|
6
|
+
* It keeps the scroll at the bottom of the container while new content is generated, but only if the user is near the bottom. If the user scrolls up to read previous content, auto-scrolling is automatically disabled so as not to interrupt their reading.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function ChatInterface() {
|
|
11
|
+
* const { ref, isAtBottom, scrollToBottom } = useAutoScroll({
|
|
12
|
+
* threshold: 50,
|
|
13
|
+
* behavior: 'smooth'
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <div>
|
|
18
|
+
* <div ref={ref} className="chat-container">
|
|
19
|
+
* {messages.map(msg => (
|
|
20
|
+
* <Message key={msg.id} content={msg.content} />
|
|
21
|
+
* ))}
|
|
22
|
+
* </div>
|
|
23
|
+
* {!isAtBottom && (
|
|
24
|
+
* <button onClick={scrollToBottom}>
|
|
25
|
+
* Ir al final ↓
|
|
26
|
+
* </button>
|
|
27
|
+
* )}
|
|
28
|
+
* </div>
|
|
29
|
+
* );
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function useAutoScroll(options) {
|
|
34
|
+
const { behavior = "auto", enabled: initialEnabled = true, threshold = 100 } = options || {};
|
|
35
|
+
const containerRef = React.useRef(null);
|
|
36
|
+
const [isAtBottom, setIsAtBottom] = React.useState(true);
|
|
37
|
+
const [autoScrollEnabled, setAutoScrollEnabled] = React.useState(initialEnabled);
|
|
38
|
+
const isScrollingProgrammatically = React.useRef(false);
|
|
39
|
+
const checkIfABottom = React.useCallback(() => {
|
|
40
|
+
const container = containerRef.current;
|
|
41
|
+
if (!container) return false;
|
|
42
|
+
const { clientHeight, scrollHeight, scrollTop } = container;
|
|
43
|
+
return scrollHeight - scrollTop - clientHeight <= threshold;
|
|
44
|
+
}, [threshold]);
|
|
45
|
+
const scrollToBottom = React.useCallback((forceSmooth) => {
|
|
46
|
+
const container = containerRef.current;
|
|
47
|
+
if (!container) return;
|
|
48
|
+
isScrollingProgrammatically.current = true;
|
|
49
|
+
container.scrollTo({
|
|
50
|
+
behavior: forceSmooth !== void 0 ? forceSmooth ? "smooth" : "auto" : behavior,
|
|
51
|
+
top: container.scrollHeight
|
|
52
|
+
});
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
isScrollingProgrammatically.current = false;
|
|
55
|
+
}, 100);
|
|
56
|
+
}, [behavior]);
|
|
57
|
+
const enableAutoScroll = React.useCallback(() => {
|
|
58
|
+
setAutoScrollEnabled(true);
|
|
59
|
+
scrollToBottom();
|
|
60
|
+
}, [scrollToBottom]);
|
|
61
|
+
const disableAutoScroll = React.useCallback(() => {
|
|
62
|
+
setAutoScrollEnabled(false);
|
|
63
|
+
}, []);
|
|
64
|
+
const handleScroll = React.useCallback(() => {
|
|
65
|
+
if (isScrollingProgrammatically.current) return;
|
|
66
|
+
const atBottom = checkIfABottom();
|
|
67
|
+
setIsAtBottom(atBottom);
|
|
68
|
+
if (!atBottom && autoScrollEnabled) setAutoScrollEnabled(false);
|
|
69
|
+
else if (atBottom && !autoScrollEnabled) setAutoScrollEnabled(true);
|
|
70
|
+
}, [checkIfABottom, autoScrollEnabled]);
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
const container = containerRef.current;
|
|
73
|
+
if (!container) return;
|
|
74
|
+
const observer = new MutationObserver(() => {
|
|
75
|
+
if (autoScrollEnabled && isAtBottom) scrollToBottom(false);
|
|
76
|
+
});
|
|
77
|
+
observer.observe(container, {
|
|
78
|
+
characterData: true,
|
|
79
|
+
childList: true,
|
|
80
|
+
subtree: true
|
|
81
|
+
});
|
|
82
|
+
return () => observer.disconnect();
|
|
83
|
+
}, [
|
|
84
|
+
autoScrollEnabled,
|
|
85
|
+
isAtBottom,
|
|
86
|
+
scrollToBottom
|
|
87
|
+
]);
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
const container = containerRef.current;
|
|
90
|
+
if (!container) return;
|
|
91
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
92
|
+
return () => container.removeEventListener("scroll", handleScroll);
|
|
93
|
+
}, []);
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
const container = containerRef.current;
|
|
96
|
+
if (!container) return;
|
|
97
|
+
const initialCheck = () => {
|
|
98
|
+
setIsAtBottom(checkIfABottom());
|
|
99
|
+
};
|
|
100
|
+
initialCheck();
|
|
101
|
+
const images = container.querySelectorAll("img");
|
|
102
|
+
images.forEach((img) => {
|
|
103
|
+
img.addEventListener("load", initialCheck);
|
|
104
|
+
});
|
|
105
|
+
return () => {
|
|
106
|
+
images.forEach((img) => {
|
|
107
|
+
img.removeEventListener("load", initialCheck);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
}, [checkIfABottom]);
|
|
111
|
+
return {
|
|
112
|
+
autoScrollEnabled,
|
|
113
|
+
disableAutoScroll,
|
|
114
|
+
enableAutoScroll,
|
|
115
|
+
isAtBottom,
|
|
116
|
+
ref: containerRef,
|
|
117
|
+
scrollToBottom
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { useAutoScroll };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useBarcode.d.ts
|
|
4
|
+
declare global {
|
|
5
|
+
interface BarcodeDetectorOptions {
|
|
6
|
+
formats?: string[];
|
|
7
|
+
}
|
|
8
|
+
interface DetectorBarcode {
|
|
9
|
+
boundingBox?: DOMRectReadOnly;
|
|
10
|
+
format: string;
|
|
11
|
+
rawValue: string;
|
|
12
|
+
}
|
|
13
|
+
class BarcodeDetector {
|
|
14
|
+
constructor(options?: BarcodeDetectorOptions);
|
|
15
|
+
static getSupportedFormats(): string[];
|
|
16
|
+
detect(image: HTMLVideoElement | HTMLImageElement | HTMLCanvasElement): Promise<DetectorBarcode[]>;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
interface UseBarcodeReturn {
|
|
20
|
+
/**
|
|
21
|
+
* Raw value of the barcode.
|
|
22
|
+
*/
|
|
23
|
+
current: DetectorBarcode | null;
|
|
24
|
+
/**
|
|
25
|
+
* A function to start the scanning process.
|
|
26
|
+
*/
|
|
27
|
+
start: () => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* A function to stop the scanning process.
|
|
30
|
+
*/
|
|
31
|
+
stop: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the browser supports BarcodeDetector API.
|
|
34
|
+
*/
|
|
35
|
+
supported: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface UseBarcodeOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Optional element to scan: video, image or canvas.
|
|
40
|
+
*/
|
|
41
|
+
elementRef?: React.RefObject<HTMLVideoElement | HTMLImageElement | HTMLCanvasElement | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Optional formats to detect (default includes most common formats).
|
|
44
|
+
*/
|
|
45
|
+
formats?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Optional callback to be called when a barcode is detected.
|
|
48
|
+
*/
|
|
49
|
+
onDetect?: (barcode: DetectorBarcode) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `useBarcode` is a hook to detect barcodes using the browser's BarcodeDetector API.
|
|
53
|
+
* Automatically handles camera permissions, stream cleanup, and updates the last detected barcode.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* const videoRef = useRef<HTMLVideoElement>(null);
|
|
58
|
+
* const { rawValue, supported } = useBarcode({ elementRef: videoRef });
|
|
59
|
+
*
|
|
60
|
+
* if (!supported) return <p>Barcode detection not supported</p>;
|
|
61
|
+
*
|
|
62
|
+
* return (
|
|
63
|
+
* <div>
|
|
64
|
+
* <video ref={videoRef} autoPlay />
|
|
65
|
+
* {rawValue && <p>Last barcode: {rawValue.rawValue}</p>}
|
|
66
|
+
* </div>
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API
|
|
70
|
+
*/
|
|
71
|
+
declare function useBarcode({
|
|
72
|
+
elementRef,
|
|
73
|
+
formats,
|
|
74
|
+
onDetect
|
|
75
|
+
}?: UseBarcodeOptions): UseBarcodeReturn;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { useBarcode };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useBarcode.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useBarcode` is a hook to detect barcodes using the browser's BarcodeDetector API.
|
|
6
|
+
* Automatically handles camera permissions, stream cleanup, and updates the last detected barcode.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const videoRef = useRef<HTMLVideoElement>(null);
|
|
11
|
+
* const { rawValue, supported } = useBarcode({ elementRef: videoRef });
|
|
12
|
+
*
|
|
13
|
+
* if (!supported) return <p>Barcode detection not supported</p>;
|
|
14
|
+
*
|
|
15
|
+
* return (
|
|
16
|
+
* <div>
|
|
17
|
+
* <video ref={videoRef} autoPlay />
|
|
18
|
+
* {rawValue && <p>Last barcode: {rawValue.rawValue}</p>}
|
|
19
|
+
* </div>
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API
|
|
23
|
+
*/
|
|
24
|
+
function useBarcode({ elementRef, formats = [
|
|
25
|
+
"aztec",
|
|
26
|
+
"code_128",
|
|
27
|
+
"code_39",
|
|
28
|
+
"code_93",
|
|
29
|
+
"codabar",
|
|
30
|
+
"data_matrix",
|
|
31
|
+
"ean_13",
|
|
32
|
+
"ean_8",
|
|
33
|
+
"itf",
|
|
34
|
+
"pdf417",
|
|
35
|
+
"qr_code",
|
|
36
|
+
"upc_a",
|
|
37
|
+
"upc_e",
|
|
38
|
+
"unknown"
|
|
39
|
+
], onDetect } = {}) {
|
|
40
|
+
const [current, setCurrent] = React.useState(null);
|
|
41
|
+
const [supported, setSupported] = React.useState(false);
|
|
42
|
+
const lastEmittedRef = React.useRef(null);
|
|
43
|
+
const animationRef = React.useRef(null);
|
|
44
|
+
const streamRef = React.useRef(null);
|
|
45
|
+
const detectorRef = React.useRef(null);
|
|
46
|
+
const runningRef = React.useRef(false);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (typeof window === "undefined" || !("BarcodeDetector" in window)) {
|
|
49
|
+
setSupported(false);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setSupported(true);
|
|
53
|
+
detectorRef.current = new BarcodeDetector({ formats });
|
|
54
|
+
}, [formats]);
|
|
55
|
+
const emitDetected = React.useCallback((barcode) => {
|
|
56
|
+
const key = `${barcode.format}-${barcode.rawValue}`;
|
|
57
|
+
if (lastEmittedRef.current === key) return;
|
|
58
|
+
lastEmittedRef.current = key;
|
|
59
|
+
setCurrent(barcode);
|
|
60
|
+
onDetect?.(barcode);
|
|
61
|
+
}, [onDetect]);
|
|
62
|
+
const scanOnce = React.useCallback(async (el) => {
|
|
63
|
+
if (!detectorRef.current) return;
|
|
64
|
+
try {
|
|
65
|
+
const barcodes = await detectorRef.current.detect(el);
|
|
66
|
+
if (barcodes.length > 0 && barcodes[0]) emitDetected(barcodes[0]);
|
|
67
|
+
else {
|
|
68
|
+
lastEmittedRef.current = null;
|
|
69
|
+
setCurrent(null);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn("useBarcode: barcode detection failed.", error);
|
|
73
|
+
}
|
|
74
|
+
}, [emitDetected]);
|
|
75
|
+
const scanLoop = React.useCallback(async (video) => {
|
|
76
|
+
if (!runningRef.current) return;
|
|
77
|
+
if (video.videoWidth === 0) {
|
|
78
|
+
animationRef.current = requestAnimationFrame(() => scanLoop(video));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await scanOnce(video);
|
|
82
|
+
animationRef.current = requestAnimationFrame(() => scanLoop(video));
|
|
83
|
+
}, [scanOnce]);
|
|
84
|
+
const start = React.useCallback(async () => {
|
|
85
|
+
if (!supported) return;
|
|
86
|
+
if (!elementRef?.current) return;
|
|
87
|
+
if (runningRef.current) return;
|
|
88
|
+
const el = elementRef.current;
|
|
89
|
+
runningRef.current = true;
|
|
90
|
+
if (el instanceof HTMLVideoElement) {
|
|
91
|
+
try {
|
|
92
|
+
streamRef.current = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
|
|
93
|
+
el.srcObject = streamRef.current;
|
|
94
|
+
el.setAttribute("playsinline", "true");
|
|
95
|
+
await el.play();
|
|
96
|
+
scanLoop(el);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
runningRef.current = false;
|
|
99
|
+
console.warn("useBarcode: camera access denied or unavailable.", error);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (el instanceof HTMLImageElement || el instanceof HTMLCanvasElement) {
|
|
104
|
+
await scanOnce(el);
|
|
105
|
+
runningRef.current = false;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
runningRef.current = false;
|
|
109
|
+
}, [
|
|
110
|
+
elementRef,
|
|
111
|
+
scanLoop,
|
|
112
|
+
scanOnce,
|
|
113
|
+
supported
|
|
114
|
+
]);
|
|
115
|
+
const stop = React.useCallback(() => {
|
|
116
|
+
runningRef.current = false;
|
|
117
|
+
if (animationRef.current !== null) {
|
|
118
|
+
cancelAnimationFrame(animationRef.current);
|
|
119
|
+
animationRef.current = null;
|
|
120
|
+
}
|
|
121
|
+
if (streamRef.current) {
|
|
122
|
+
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
123
|
+
streamRef.current = null;
|
|
124
|
+
}
|
|
125
|
+
lastEmittedRef.current = null;
|
|
126
|
+
setCurrent(null);
|
|
127
|
+
}, []);
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
return () => stop();
|
|
130
|
+
}, []);
|
|
131
|
+
return {
|
|
132
|
+
current,
|
|
133
|
+
start,
|
|
134
|
+
stop,
|
|
135
|
+
supported
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
export { useBarcode };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/useBatteryStatus.d.ts
|
|
2
|
+
interface UseBatteryStatus {
|
|
3
|
+
/** Indicates whether the device is currently charging. */
|
|
4
|
+
charging: boolean | null;
|
|
5
|
+
/** Time in seconds until the battery is fully charged. `Infinity` means the battery is already full or charging is not applicable. */
|
|
6
|
+
chargingTime: number | null;
|
|
7
|
+
/** Time in seconds until the battery is completely discharged. `Infinity` means the system cannot determine the remaining time. */
|
|
8
|
+
dischargingTime: number | null;
|
|
9
|
+
/** Battery level represented as a value between 0.0 and 1.0 */
|
|
10
|
+
level: number | null;
|
|
11
|
+
}
|
|
12
|
+
interface UseBatteryStatusReturn {
|
|
13
|
+
/** Raw battery information. Values are `null` until the Battery Manager is resolved. */
|
|
14
|
+
battery: UseBatteryStatus;
|
|
15
|
+
/** Indicates whether the Battery Status API is supported in the current runtime environment. */
|
|
16
|
+
isSupported: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare global {
|
|
19
|
+
interface BatteryManager extends EventTarget {
|
|
20
|
+
addEventListener(type: 'chargingchange' | 'levelchange' | 'chargingtimechange' | 'dischargingtimechange', listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
21
|
+
readonly charging: boolean;
|
|
22
|
+
readonly chargingTime: number;
|
|
23
|
+
readonly dischargingTime: number;
|
|
24
|
+
readonly level: number;
|
|
25
|
+
removeEventListener(type: 'chargingchange' | 'levelchange' | 'chargingtimechange' | 'dischargingtimechange', listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
26
|
+
}
|
|
27
|
+
interface Navigator {
|
|
28
|
+
getBattery(): Promise<BatteryManager>;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `useBatteryStatus`is a hook that provides information about the system's battery charge level and lets you be notified by events that are sent when the battery level or charging status changes.
|
|
33
|
+
* This hook uses the native 'Battery Status API and it is not available in Web Workers and it is available only in secure contexts (HTTPS).
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* const { isSupported, battery } = useBatteryStatus();
|
|
38
|
+
*
|
|
39
|
+
* if (!isSupported) {
|
|
40
|
+
* return <span>Battery API not supported</span>;
|
|
41
|
+
* }
|
|
42
|
+
* return (
|
|
43
|
+
* <div>
|
|
44
|
+
* <p>Charging: {battery.charging ? 'Yes' : 'No'}</p>
|
|
45
|
+
* <p>Level: {battery.level}</p>
|
|
46
|
+
* </div>
|
|
47
|
+
);
|
|
48
|
+
* ```
|
|
49
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API
|
|
50
|
+
*/
|
|
51
|
+
declare function useBatteryStatus(): UseBatteryStatusReturn;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { useBatteryStatus };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useBatteryStatus.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useBatteryStatus`is a hook that provides information about the system's battery charge level and lets you be notified by events that are sent when the battery level or charging status changes.
|
|
6
|
+
* This hook uses the native 'Battery Status API and it is not available in Web Workers and it is available only in secure contexts (HTTPS).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { isSupported, battery } = useBatteryStatus();
|
|
11
|
+
*
|
|
12
|
+
* if (!isSupported) {
|
|
13
|
+
* return <span>Battery API not supported</span>;
|
|
14
|
+
* }
|
|
15
|
+
* return (
|
|
16
|
+
* <div>
|
|
17
|
+
* <p>Charging: {battery.charging ? 'Yes' : 'No'}</p>
|
|
18
|
+
* <p>Level: {battery.level}</p>
|
|
19
|
+
* </div>
|
|
20
|
+
);
|
|
21
|
+
* ```
|
|
22
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API
|
|
23
|
+
*/
|
|
24
|
+
function useBatteryStatus() {
|
|
25
|
+
const isSupported = typeof navigator !== "undefined" && typeof navigator.getBattery === "function";
|
|
26
|
+
const [battery, setBattery] = React.useState({
|
|
27
|
+
charging: null,
|
|
28
|
+
chargingTime: null,
|
|
29
|
+
dischargingTime: null,
|
|
30
|
+
level: null
|
|
31
|
+
});
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (!isSupported) return;
|
|
34
|
+
let batteryManager = null;
|
|
35
|
+
const updateBatteryState = () => {
|
|
36
|
+
if (!batteryManager) return;
|
|
37
|
+
setBattery({
|
|
38
|
+
charging: batteryManager.charging,
|
|
39
|
+
chargingTime: batteryManager.chargingTime,
|
|
40
|
+
dischargingTime: batteryManager.dischargingTime,
|
|
41
|
+
level: batteryManager.level
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
navigator.getBattery().then((bm) => {
|
|
45
|
+
batteryManager = bm;
|
|
46
|
+
updateBatteryState();
|
|
47
|
+
bm.addEventListener("chargingchange", updateBatteryState);
|
|
48
|
+
bm.addEventListener("levelchange", updateBatteryState);
|
|
49
|
+
bm.addEventListener("chargingtimechange", updateBatteryState);
|
|
50
|
+
bm.addEventListener("dischargingtimechange", updateBatteryState);
|
|
51
|
+
});
|
|
52
|
+
return () => {
|
|
53
|
+
if (!batteryManager) return;
|
|
54
|
+
batteryManager.removeEventListener("chargingchange", updateBatteryState);
|
|
55
|
+
batteryManager.removeEventListener("levelchange", updateBatteryState);
|
|
56
|
+
batteryManager.removeEventListener("chargingtimechange", updateBatteryState);
|
|
57
|
+
batteryManager.removeEventListener("dischargingtimechange", updateBatteryState);
|
|
58
|
+
};
|
|
59
|
+
}, [isSupported]);
|
|
60
|
+
return {
|
|
61
|
+
battery,
|
|
62
|
+
isSupported
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { useBatteryStatus };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/useBodyScrollFreeze.d.ts
|
|
2
|
+
type ScrollAxis = 'x' | 'y' | 'both';
|
|
3
|
+
interface BodyScrollFreezeOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Axis to freeze.
|
|
6
|
+
* - 'x' → horizontal scroll
|
|
7
|
+
* - 'y' → vertical scroll
|
|
8
|
+
* - 'both' → horizontal and vertical scroll
|
|
9
|
+
*
|
|
10
|
+
* @default 'y'
|
|
11
|
+
*/
|
|
12
|
+
axis?: ScrollAxis;
|
|
13
|
+
}
|
|
14
|
+
interface BodyScrollFreezeReturn {
|
|
15
|
+
freeze: (options?: BodyScrollFreezeOptions) => void;
|
|
16
|
+
unfreeze: () => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `useBodyScrollFreeze` is an unopinionated React hook that provides an imperative API to disable body scrolling on one or both axes.
|
|
20
|
+
* The hook captures the original `overflowX` and `overflowY` values and restores them exactly when the component is unmounted.
|
|
21
|
+
*
|
|
22
|
+
* The hook does not automatically lock scrolling; consumers decide
|
|
23
|
+
* when and how to apply it.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const { freeze } = useBodyScrollFreeze();
|
|
28
|
+
*
|
|
29
|
+
* freeze(); // freezes vertical scroll (default)
|
|
30
|
+
* freeze({ axis: 'both' });
|
|
31
|
+
* freeze({ axis: 'x' });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function useBodyScrollFreeze(): BodyScrollFreezeReturn;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { useBodyScrollFreeze };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useBodyScrollFreeze.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useBodyScrollFreeze` is an unopinionated React hook that provides an imperative API to disable body scrolling on one or both axes.
|
|
6
|
+
* The hook captures the original `overflowX` and `overflowY` values and restores them exactly when the component is unmounted.
|
|
7
|
+
*
|
|
8
|
+
* The hook does not automatically lock scrolling; consumers decide
|
|
9
|
+
* when and how to apply it.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const { freeze } = useBodyScrollFreeze();
|
|
14
|
+
*
|
|
15
|
+
* freeze(); // freezes vertical scroll (default)
|
|
16
|
+
* freeze({ axis: 'both' });
|
|
17
|
+
* freeze({ axis: 'x' });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function useBodyScrollFreeze() {
|
|
21
|
+
const originalStyles = React.useRef(null);
|
|
22
|
+
const freeze = React.useCallback((options) => {
|
|
23
|
+
if (typeof window === "undefined") return;
|
|
24
|
+
const axis = options?.axis ?? "y";
|
|
25
|
+
const body = document.body;
|
|
26
|
+
if (!originalStyles.current) originalStyles.current = {
|
|
27
|
+
overflowX: body.style.overflowX,
|
|
28
|
+
overflowY: body.style.overflowY,
|
|
29
|
+
position: body.style.position,
|
|
30
|
+
scrollY: window.scrollY,
|
|
31
|
+
top: body.style.top,
|
|
32
|
+
width: body.style.width
|
|
33
|
+
};
|
|
34
|
+
body.style.position = "fixed";
|
|
35
|
+
body.style.top = `-${originalStyles.current.scrollY}px`;
|
|
36
|
+
body.style.width = "100%";
|
|
37
|
+
if (axis === "x" || axis === "both") body.style.overflowX = "hidden";
|
|
38
|
+
if (axis === "y" || axis === "both") body.style.overflowY = "hidden";
|
|
39
|
+
}, []);
|
|
40
|
+
const unfreeze = React.useCallback(() => {
|
|
41
|
+
if (typeof window === "undefined") return;
|
|
42
|
+
if (!originalStyles.current) return;
|
|
43
|
+
const body = document.body;
|
|
44
|
+
const { overflowX, overflowY, position, scrollY, top, width } = originalStyles.current;
|
|
45
|
+
body.style.position = position;
|
|
46
|
+
body.style.top = top;
|
|
47
|
+
body.style.width = width;
|
|
48
|
+
body.style.overflowX = overflowX;
|
|
49
|
+
body.style.overflowY = overflowY;
|
|
50
|
+
window.scrollTo(0, scrollY);
|
|
51
|
+
originalStyles.current = null;
|
|
52
|
+
}, []);
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
return () => {
|
|
55
|
+
if (typeof window === "undefined") return;
|
|
56
|
+
if (!originalStyles.current) return;
|
|
57
|
+
const body = document.body;
|
|
58
|
+
const { overflowX, overflowY, position, scrollY, top, width } = originalStyles.current;
|
|
59
|
+
body.style.position = position;
|
|
60
|
+
body.style.top = top;
|
|
61
|
+
body.style.width = width;
|
|
62
|
+
body.style.overflowX = overflowX;
|
|
63
|
+
body.style.overflowY = overflowY;
|
|
64
|
+
window.scrollTo(0, scrollY);
|
|
65
|
+
};
|
|
66
|
+
}, []);
|
|
67
|
+
return {
|
|
68
|
+
freeze,
|
|
69
|
+
unfreeze
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { useBodyScrollFreeze };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useCameraCapture.d.ts
|
|
4
|
+
type CameraCaptureFormat = 'image/jpeg' | 'image/png';
|
|
5
|
+
interface CameraCaptureOutput {
|
|
6
|
+
/**
|
|
7
|
+
* Image quality (0.0 - 1.0)
|
|
8
|
+
* Only applies to lossy formats (JPEG).
|
|
9
|
+
*/
|
|
10
|
+
quality?: number;
|
|
11
|
+
/**
|
|
12
|
+
* MIME type of the captured image.
|
|
13
|
+
*/
|
|
14
|
+
type: CameraCaptureFormat;
|
|
15
|
+
}
|
|
16
|
+
interface UseCameraCaptureOptions {
|
|
17
|
+
format?: CameraCaptureOutput;
|
|
18
|
+
/**
|
|
19
|
+
* Optional callback executed after a photo is captured.
|
|
20
|
+
* Provides both the base64 data URL and the Blob representation.
|
|
21
|
+
*/
|
|
22
|
+
onCapture?: (dataUrl: string, blob: Blob) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Desired output width for the captured image.
|
|
25
|
+
* The height is automatically calculated to preserve the aspect ratio.
|
|
26
|
+
*
|
|
27
|
+
* @default 320
|
|
28
|
+
*/
|
|
29
|
+
width?: number;
|
|
30
|
+
}
|
|
31
|
+
interface UseCameraCaptureReturn {
|
|
32
|
+
/**
|
|
33
|
+
* Ref to the offscreen HTMLCanvasElement used for frame capture.
|
|
34
|
+
*/
|
|
35
|
+
canvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Captures the current video frame and renders it to the canvas.
|
|
38
|
+
*/
|
|
39
|
+
capture: () => string | null;
|
|
40
|
+
/**
|
|
41
|
+
* Ref to the HTMLImageElement where the captured photo can be rendered.
|
|
42
|
+
*/
|
|
43
|
+
imageRef: React.RefObject<HTMLImageElement | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Requests camera permissions and starts the video stream.
|
|
46
|
+
* Can be safely called again if the user initially denied access.
|
|
47
|
+
*/
|
|
48
|
+
requestPermission: () => Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Stops all active media tracks and releases the camera.
|
|
51
|
+
*/
|
|
52
|
+
stop: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Indicates whether camera permissions has been granted.
|
|
55
|
+
*/
|
|
56
|
+
usePermission: () => boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Indicates whether the video stream is currently active.
|
|
59
|
+
*/
|
|
60
|
+
useStreaming: () => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Ref to the HTMLVideoElement that renders the live camera stream.
|
|
63
|
+
*/
|
|
64
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Unopinionated, SSR-safe React hook for capturing still photos from the user's camera using getUserMedia and Canvas.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* const { videoRef, requestPermission, capture } = useCameraCapture();
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function useCameraCapture(options?: UseCameraCaptureOptions): UseCameraCaptureReturn;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { useCameraCapture };
|