@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,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 };