@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,138 @@
|
|
|
1
|
+
//#region src/useNetworkInformation.d.ts
|
|
2
|
+
interface NetworkInformation extends EventTarget {
|
|
3
|
+
/**
|
|
4
|
+
* Estimated effective bandwidth of the user's connection, in megabits per second (Mb/s).
|
|
5
|
+
*
|
|
6
|
+
* This value is a browser-provided heuristic based on recently observed network performance.
|
|
7
|
+
* It does NOT represent the user's contracted bandwidth nor a speed-test result.
|
|
8
|
+
*
|
|
9
|
+
* ### Practical interpretation (approximate)
|
|
10
|
+
* - < 1 Mb/s → very slow connection (avoid heavy assets)
|
|
11
|
+
* - 1 – 3 Mb/s → slow connection
|
|
12
|
+
* - 3 – 10 Mb/s → moderate connection
|
|
13
|
+
* - > 10 Mb/s → fast connection
|
|
14
|
+
*
|
|
15
|
+
* ### Recommended usage
|
|
16
|
+
* - Adapt image quality and media bitrates.
|
|
17
|
+
* - Reduce prefetching and background requests on low values.
|
|
18
|
+
*
|
|
19
|
+
* ### Notes
|
|
20
|
+
* - The value may fluctuate over time.
|
|
21
|
+
* - Not available in all browsers.
|
|
22
|
+
*
|
|
23
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
|
|
24
|
+
*/
|
|
25
|
+
readonly downlink?: number | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum theoretical bandwidth of the underlying network link, in megabits per second (Mb/s).
|
|
28
|
+
*
|
|
29
|
+
* This represents the estimated capacity of the physical or logical connection
|
|
30
|
+
* (e.g., Wi-Fi, Ethernet), not the observed throughput.
|
|
31
|
+
*
|
|
32
|
+
* ### Typical values
|
|
33
|
+
* - Cellular networks → low to moderate
|
|
34
|
+
* - Wi-Fi / Ethernet → high or Infinity
|
|
35
|
+
*
|
|
36
|
+
* ### Recommended usage
|
|
37
|
+
* - Decide whether large downloads are feasible.
|
|
38
|
+
* - Compare against `downlink` to detect congestion.
|
|
39
|
+
*
|
|
40
|
+
* ### Notes
|
|
41
|
+
* - Often undefined or `Infinity`.
|
|
42
|
+
* - Not consistently implemented across browsers.
|
|
43
|
+
*
|
|
44
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlinkMax
|
|
45
|
+
*/
|
|
46
|
+
readonly downlinkMax?: number | undefined;
|
|
47
|
+
readonly effectiveType?: 'slow-2g' | '2g' | '3g' | '4g' | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Estimated round-trip time (RTT), in milliseconds (ms).
|
|
50
|
+
*
|
|
51
|
+
* RTT measures the time it takes for a network request to travel from the client
|
|
52
|
+
* to the server and back.
|
|
53
|
+
*
|
|
54
|
+
* ### Practical interpretation (approximate)
|
|
55
|
+
* - < 50 ms → excellent latency
|
|
56
|
+
* - 50 – 150 ms → acceptable
|
|
57
|
+
* - 150 – 300 ms → noticeable latency
|
|
58
|
+
* - > 300 ms → high latency (degraded real-time UX)
|
|
59
|
+
*
|
|
60
|
+
* ### Recommended usage
|
|
61
|
+
* - Tune request timeouts.
|
|
62
|
+
* - Decide between polling vs real-time transports.
|
|
63
|
+
* - Disable latency-sensitive features on high RTT.
|
|
64
|
+
*
|
|
65
|
+
* ### Notes
|
|
66
|
+
* - Values are estimates, not precise measurements.
|
|
67
|
+
* - RTT may vary significantly on mobile networks.
|
|
68
|
+
*
|
|
69
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/rtt
|
|
70
|
+
*/
|
|
71
|
+
readonly rtt?: number | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Indicates whether the user has enabled a reduced data usage mode (Data Saver).
|
|
74
|
+
*
|
|
75
|
+
* This is an explicit user preference, not a performance metric.
|
|
76
|
+
* When `true`, the user is signaling intent to minimize network usage.
|
|
77
|
+
*
|
|
78
|
+
* ### Recommended behavior when enabled
|
|
79
|
+
* - Serve lower-resolution images.
|
|
80
|
+
* - Disable autoplay media.
|
|
81
|
+
* - Avoid aggressive prefetching.
|
|
82
|
+
* - Reduce background network activity.
|
|
83
|
+
*
|
|
84
|
+
* ### Priority rule
|
|
85
|
+
* `saveData === true` SHOULD override optimistic decisions based on
|
|
86
|
+
* `downlink` or `downlinkMax`.
|
|
87
|
+
*
|
|
88
|
+
* ### Notes
|
|
89
|
+
* - Availability depends on browser and OS.
|
|
90
|
+
*
|
|
91
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData
|
|
92
|
+
*/
|
|
93
|
+
readonly saveData?: boolean | undefined;
|
|
94
|
+
readonly type?: 'bluetooth' | 'cellular' | 'ethernet' | 'node' | 'wifi' | 'wimax' | 'other' | 'unknown' | undefined;
|
|
95
|
+
}
|
|
96
|
+
declare global {
|
|
97
|
+
interface Navigator {
|
|
98
|
+
connection: NetworkInformation;
|
|
99
|
+
mozConnection: NetworkInformation;
|
|
100
|
+
webkitConnection: NetworkInformation;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
interface NetworkInformationSnapshot {
|
|
104
|
+
downlink?: NetworkInformation['downlink'];
|
|
105
|
+
downlinkMax?: NetworkInformation['downlinkMax'];
|
|
106
|
+
effectiveType?: NetworkInformation['effectiveType'];
|
|
107
|
+
rtt?: NetworkInformation['rtt'];
|
|
108
|
+
saveData?: NetworkInformation['saveData'];
|
|
109
|
+
type?: NetworkInformation['type'];
|
|
110
|
+
}
|
|
111
|
+
interface UseNetworkInformationReturn extends NetworkInformationSnapshot {
|
|
112
|
+
supported: boolean;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* `useNetworkInformation` returns live network information using the Network Information API.
|
|
116
|
+
* The hook automatically updates when the underlying connection changes.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const network = useNetworkInformation();
|
|
121
|
+
*
|
|
122
|
+
* if (!network.supported) {
|
|
123
|
+
* return <span>Network API not supported</span>;
|
|
124
|
+
* }
|
|
125
|
+
*
|
|
126
|
+
* return (
|
|
127
|
+
* <div>
|
|
128
|
+
* <p>Type: {network.type}</p>
|
|
129
|
+
* <p>Effective type: {network.effectiveType}</p>
|
|
130
|
+
* <p>Downlink: {network.downlink} Mb/s</p>
|
|
131
|
+
* </div>
|
|
132
|
+
* );
|
|
133
|
+
* ```
|
|
134
|
+
* @see https://developer.mozilla.org/es/docs/Web/API/Network_Information_API
|
|
135
|
+
*/
|
|
136
|
+
declare function useNetworkInformation(): UseNetworkInformationReturn;
|
|
137
|
+
//#endregion
|
|
138
|
+
export { useNetworkInformation };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useNetworkInformation.ts
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
let listening = false;
|
|
6
|
+
function getConnection() {
|
|
7
|
+
if (typeof navigator === "undefined") return null;
|
|
8
|
+
return navigator.connection ?? navigator.mozConnection ?? navigator.webkitConnection ?? null;
|
|
9
|
+
}
|
|
10
|
+
let lastSnapshot = null;
|
|
11
|
+
function getSnapshot() {
|
|
12
|
+
const connection = getConnection();
|
|
13
|
+
if (!connection) return null;
|
|
14
|
+
const nextSnapshot = {
|
|
15
|
+
downlink: connection.downlink,
|
|
16
|
+
downlinkMax: connection.downlinkMax,
|
|
17
|
+
effectiveType: connection.effectiveType,
|
|
18
|
+
rtt: connection.rtt,
|
|
19
|
+
saveData: connection.saveData,
|
|
20
|
+
type: connection.type
|
|
21
|
+
};
|
|
22
|
+
if (lastSnapshot && lastSnapshot.downlink === nextSnapshot.downlink && lastSnapshot.downlinkMax === nextSnapshot.downlinkMax && lastSnapshot.effectiveType === nextSnapshot.effectiveType && lastSnapshot.rtt === nextSnapshot.rtt && lastSnapshot.saveData === nextSnapshot.saveData && lastSnapshot.type === nextSnapshot.type) return lastSnapshot;
|
|
23
|
+
lastSnapshot = nextSnapshot;
|
|
24
|
+
return nextSnapshot;
|
|
25
|
+
}
|
|
26
|
+
function emit() {
|
|
27
|
+
listeners.forEach((listener) => listener());
|
|
28
|
+
}
|
|
29
|
+
function subscribe(listener) {
|
|
30
|
+
listeners.add(listener);
|
|
31
|
+
const connection = getConnection();
|
|
32
|
+
if (!listening && connection) {
|
|
33
|
+
connection.addEventListener("change", emit);
|
|
34
|
+
listening = true;
|
|
35
|
+
}
|
|
36
|
+
return () => {
|
|
37
|
+
listeners.delete(listener);
|
|
38
|
+
if (listeners.size === 0 && connection && listening) {
|
|
39
|
+
connection.removeEventListener("change", emit);
|
|
40
|
+
listening = false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `useNetworkInformation` returns live network information using the Network Information API.
|
|
46
|
+
* The hook automatically updates when the underlying connection changes.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const network = useNetworkInformation();
|
|
51
|
+
*
|
|
52
|
+
* if (!network.supported) {
|
|
53
|
+
* return <span>Network API not supported</span>;
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* return (
|
|
57
|
+
* <div>
|
|
58
|
+
* <p>Type: {network.type}</p>
|
|
59
|
+
* <p>Effective type: {network.effectiveType}</p>
|
|
60
|
+
* <p>Downlink: {network.downlink} Mb/s</p>
|
|
61
|
+
* </div>
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
* @see https://developer.mozilla.org/es/docs/Web/API/Network_Information_API
|
|
65
|
+
*/
|
|
66
|
+
function useNetworkInformation() {
|
|
67
|
+
const snapshot = React.useSyncExternalStore(subscribe, getSnapshot, () => null);
|
|
68
|
+
if (!snapshot) return { supported: false };
|
|
69
|
+
return {
|
|
70
|
+
supported: true,
|
|
71
|
+
...snapshot
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { useNetworkInformation };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/useOnline.d.ts
|
|
2
|
+
interface UseOnlineReturn {
|
|
3
|
+
/**
|
|
4
|
+
* Whether the browser is considered online.
|
|
5
|
+
* Note: `true` does not guarantee internet access.
|
|
6
|
+
*/
|
|
7
|
+
online: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* `useOnline` is an unopinionated hook that exposes the browser's online status based on the Navigator.onLine API.
|
|
11
|
+
* It automatically stays in sync with `online` and `offline` events.
|
|
12
|
+
*
|
|
13
|
+
* @see https://developer.mozilla.org/es/docs/Web/API/Navigator/onLine
|
|
14
|
+
*/
|
|
15
|
+
declare function useOnline(): UseOnlineReturn;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useOnline };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useOnline.ts
|
|
4
|
+
function suscribe(callback) {
|
|
5
|
+
window.addEventListener("online", callback);
|
|
6
|
+
window.addEventListener("offline", callback);
|
|
7
|
+
return () => {
|
|
8
|
+
window.removeEventListener("online", callback);
|
|
9
|
+
window.removeEventListener("offline", callback);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function getSnapshot() {
|
|
13
|
+
return navigator.onLine;
|
|
14
|
+
}
|
|
15
|
+
function getServerSnapshot() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `useOnline` is an unopinionated hook that exposes the browser's online status based on the Navigator.onLine API.
|
|
20
|
+
* It automatically stays in sync with `online` and `offline` events.
|
|
21
|
+
*
|
|
22
|
+
* @see https://developer.mozilla.org/es/docs/Web/API/Navigator/onLine
|
|
23
|
+
*/
|
|
24
|
+
function useOnline() {
|
|
25
|
+
return { online: React.useSyncExternalStore(suscribe, getSnapshot, getServerSnapshot) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { useOnline };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/usePageVisibility.d.ts
|
|
2
|
+
interface PageVisibilityReturn {
|
|
3
|
+
/**
|
|
4
|
+
* Whether the document is currently visible.
|
|
5
|
+
*/
|
|
6
|
+
isVisible: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Current visibility state of the document.
|
|
9
|
+
*/
|
|
10
|
+
visibilityState: DocumentVisibilityState;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `usePageVisibility` is a React hook that exposes the current visibility
|
|
14
|
+
* state of the document using the Page Visibility API.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function PollingController() {
|
|
19
|
+
* const { isVisible } = usePageVisibility();
|
|
20
|
+
*
|
|
21
|
+
* React.useEffect(() => {
|
|
22
|
+
* if (!isVisible) pausePolling();
|
|
23
|
+
* else resumePolling();
|
|
24
|
+
* }, [isVisible]);
|
|
25
|
+
*
|
|
26
|
+
* return null;s
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function usePageVisibility(): PageVisibilityReturn;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { usePageVisibility };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePageVisibility.ts
|
|
4
|
+
function visibilityStore() {
|
|
5
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
let listening = false;
|
|
7
|
+
function emit() {
|
|
8
|
+
listeners.forEach((listener) => listener());
|
|
9
|
+
}
|
|
10
|
+
function subscribe(listener) {
|
|
11
|
+
listeners.add(listener);
|
|
12
|
+
if (!listening && typeof document !== "undefined") {
|
|
13
|
+
document.addEventListener("visibilitychange", emit);
|
|
14
|
+
listening = true;
|
|
15
|
+
}
|
|
16
|
+
return () => {
|
|
17
|
+
listeners.delete(listener);
|
|
18
|
+
if (listeners.size === 0 && listening && typeof document !== "undefined") {
|
|
19
|
+
document.removeEventListener("visibilitychange", emit);
|
|
20
|
+
listening = false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function getSnapshot() {
|
|
25
|
+
if (typeof document === "undefined") return "visible";
|
|
26
|
+
return document.visibilityState;
|
|
27
|
+
}
|
|
28
|
+
function getServerSnapshot() {
|
|
29
|
+
return "visible";
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
getServerSnapshot,
|
|
33
|
+
getSnapshot,
|
|
34
|
+
subscribe
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const pageVisibilityStore = visibilityStore();
|
|
38
|
+
/**
|
|
39
|
+
* `usePageVisibility` is a React hook that exposes the current visibility
|
|
40
|
+
* state of the document using the Page Visibility API.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* function PollingController() {
|
|
45
|
+
* const { isVisible } = usePageVisibility();
|
|
46
|
+
*
|
|
47
|
+
* React.useEffect(() => {
|
|
48
|
+
* if (!isVisible) pausePolling();
|
|
49
|
+
* else resumePolling();
|
|
50
|
+
* }, [isVisible]);
|
|
51
|
+
*
|
|
52
|
+
* return null;s
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function usePageVisibility() {
|
|
57
|
+
const visibilityState = React.useSyncExternalStore(pageVisibilityStore.subscribe, pageVisibilityStore.getSnapshot, pageVisibilityStore.getServerSnapshot);
|
|
58
|
+
return {
|
|
59
|
+
isVisible: visibilityState === "visible",
|
|
60
|
+
visibilityState
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { usePageVisibility };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/usePermissions.d.ts
|
|
2
|
+
type PermissionState = 'granted' | 'denied' | 'prompt';
|
|
3
|
+
type PermissionsSnapshot = Partial<Record<PermissionName, PermissionState>>;
|
|
4
|
+
interface UsePermissionReturn {
|
|
5
|
+
isSupported: boolean;
|
|
6
|
+
permissions: PermissionsSnapshot;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `usePermissions` is a React hook unopinionated to observe in real-time the permission status of browser using the Permissions API.
|
|
10
|
+
* This hook does not manage permissions, it only observes them.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { permissions, isSupported } = usePermissions([
|
|
15
|
+
* 'camera',
|
|
16
|
+
* 'microphone',
|
|
17
|
+
* 'geolocation'
|
|
18
|
+
* ]);
|
|
19
|
+
*
|
|
20
|
+
* if (permissions.camera === 'denied') {
|
|
21
|
+
* // Show a message to the user
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API
|
|
25
|
+
*/
|
|
26
|
+
declare function usePermissions<T extends readonly PermissionName[]>(permissionNames: T): UsePermissionReturn;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { usePermissions };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePermissions.ts
|
|
4
|
+
/**
|
|
5
|
+
* `usePermissions` is a React hook unopinionated to observe in real-time the permission status of browser using the Permissions API.
|
|
6
|
+
* This hook does not manage permissions, it only observes them.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { permissions, isSupported } = usePermissions([
|
|
11
|
+
* 'camera',
|
|
12
|
+
* 'microphone',
|
|
13
|
+
* 'geolocation'
|
|
14
|
+
* ]);
|
|
15
|
+
*
|
|
16
|
+
* if (permissions.camera === 'denied') {
|
|
17
|
+
* // Show a message to the user
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API
|
|
21
|
+
*/
|
|
22
|
+
function usePermissions(permissionNames) {
|
|
23
|
+
const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && "permissions" in navigator;
|
|
24
|
+
const [permissions, setPermissions] = React.useState({});
|
|
25
|
+
const statusRef = React.useRef({});
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
if (!isSupported) return;
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
const cleanups = [];
|
|
30
|
+
const setupPermission = async (name) => {
|
|
31
|
+
try {
|
|
32
|
+
const status = await navigator.permissions.query({ name });
|
|
33
|
+
if (cancelled) return;
|
|
34
|
+
statusRef.current[name] = status;
|
|
35
|
+
setPermissions((prev) => {
|
|
36
|
+
if (prev[name] === status.state) return prev;
|
|
37
|
+
return {
|
|
38
|
+
...prev,
|
|
39
|
+
[name]: status.state
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
const handleChange = () => {
|
|
43
|
+
setPermissions((prev) => {
|
|
44
|
+
if (prev[name] === status.state) return prev;
|
|
45
|
+
return {
|
|
46
|
+
...prev,
|
|
47
|
+
[name]: status.state
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
status.addEventListener("change", handleChange);
|
|
52
|
+
cleanups.push(() => {
|
|
53
|
+
status.removeEventListener("change", handleChange);
|
|
54
|
+
});
|
|
55
|
+
} catch {}
|
|
56
|
+
};
|
|
57
|
+
for (const name of permissionNames) setupPermission(name);
|
|
58
|
+
return () => {
|
|
59
|
+
cancelled = true;
|
|
60
|
+
cleanups.forEach((fn) => fn());
|
|
61
|
+
};
|
|
62
|
+
}, [isSupported, permissionNames]);
|
|
63
|
+
return {
|
|
64
|
+
isSupported,
|
|
65
|
+
permissions
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { usePermissions };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePictureInPicture.d.ts
|
|
4
|
+
interface UsePictureInPictureReturn {
|
|
5
|
+
/**
|
|
6
|
+
* Requests Picture-in-Picture for the attached video element.
|
|
7
|
+
*/
|
|
8
|
+
enter: () => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Exits Picture-in-Picture mode if active.
|
|
11
|
+
*/
|
|
12
|
+
exit: () => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the video is currently in Picture-in-Picture mode.
|
|
15
|
+
*/
|
|
16
|
+
isActive: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether Picture-in-Picture is supported by the browser.
|
|
19
|
+
*/
|
|
20
|
+
isSupported: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Ref to the HTMLVideoElement.
|
|
23
|
+
*/
|
|
24
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `usePictureInPicture` provides unopinionated access to the Picture-in-Picture Web API for HTMLVideoElement.
|
|
28
|
+
* It manages PiP lifecycle and state but leaves UI and UX decisions to the consumer.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const pip = usePictureInPicture();
|
|
33
|
+
*
|
|
34
|
+
* return (
|
|
35
|
+
* <>
|
|
36
|
+
* <video ref={pip.videoRef} src="/video.mp4" controls />
|
|
37
|
+
* <button onClick={pip.enter} disabled={!pip.isSupported}>
|
|
38
|
+
* PiP
|
|
39
|
+
* </button>
|
|
40
|
+
* </>
|
|
41
|
+
* );
|
|
42
|
+
* ```
|
|
43
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API
|
|
44
|
+
*/
|
|
45
|
+
declare function usePictureInPicture(): UsePictureInPictureReturn;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { usePictureInPicture };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePictureInPicture.ts
|
|
4
|
+
/**
|
|
5
|
+
* `usePictureInPicture` provides unopinionated access to the Picture-in-Picture Web API for HTMLVideoElement.
|
|
6
|
+
* It manages PiP lifecycle and state but leaves UI and UX decisions to the consumer.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const pip = usePictureInPicture();
|
|
11
|
+
*
|
|
12
|
+
* return (
|
|
13
|
+
* <>
|
|
14
|
+
* <video ref={pip.videoRef} src="/video.mp4" controls />
|
|
15
|
+
* <button onClick={pip.enter} disabled={!pip.isSupported}>
|
|
16
|
+
* PiP
|
|
17
|
+
* </button>
|
|
18
|
+
* </>
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API
|
|
22
|
+
*/
|
|
23
|
+
function usePictureInPicture() {
|
|
24
|
+
const videoRef = React.useRef(null);
|
|
25
|
+
const [isActive, setIsActive] = React.useState(false);
|
|
26
|
+
const isSupported = typeof document !== "undefined" && "pictureInPictureEnabled" in document;
|
|
27
|
+
const enter = React.useCallback(async () => {
|
|
28
|
+
if (!isSupported) return;
|
|
29
|
+
const video = videoRef.current;
|
|
30
|
+
if (!video) return;
|
|
31
|
+
if (document.pictureInPictureElement) return;
|
|
32
|
+
await video.requestPictureInPicture();
|
|
33
|
+
}, [isSupported]);
|
|
34
|
+
const exit = React.useCallback(async () => {
|
|
35
|
+
if (!isSupported) return;
|
|
36
|
+
if (!document.pictureInPictureElement) return;
|
|
37
|
+
await document.exitPictureInPicture();
|
|
38
|
+
}, [isSupported]);
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
if (!isSupported) return;
|
|
41
|
+
const onEnter = () => setIsActive(true);
|
|
42
|
+
const onExit = () => setIsActive(false);
|
|
43
|
+
document.addEventListener("enterpictureinpicture", onEnter);
|
|
44
|
+
document.addEventListener("leavepictureinpicture", onExit);
|
|
45
|
+
return () => {
|
|
46
|
+
document.removeEventListener("enterpictureinpicture", onEnter);
|
|
47
|
+
document.removeEventListener("leavepictureinpicture", onExit);
|
|
48
|
+
};
|
|
49
|
+
}, [isSupported]);
|
|
50
|
+
return {
|
|
51
|
+
enter,
|
|
52
|
+
exit,
|
|
53
|
+
isActive,
|
|
54
|
+
isSupported,
|
|
55
|
+
videoRef
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { usePictureInPicture };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePopover.d.ts
|
|
4
|
+
interface UsePopoverReturn<TAnchor extends HTMLElement, TPopover extends HTMLElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Ref for the anchor (trigger) element.
|
|
7
|
+
*/
|
|
8
|
+
anchorRef: React.RefObject<TAnchor | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Closes the popover.
|
|
11
|
+
*/
|
|
12
|
+
close: () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the popover is open.
|
|
15
|
+
*/
|
|
16
|
+
isOpen: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Opens the popover.
|
|
19
|
+
*/
|
|
20
|
+
open: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Ref for the popover element.
|
|
23
|
+
*/
|
|
24
|
+
popoverRef: React.RefObject<TPopover | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Toggles the popover.
|
|
27
|
+
*/
|
|
28
|
+
toggle: () => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `usePopover` is React hook that manages the open/close state and lifecycle of a popover anchored to a trigger element.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* const popover = usePopover();
|
|
36
|
+
*
|
|
37
|
+
* return (
|
|
38
|
+
* <>
|
|
39
|
+
* <button ref={popover.anchorRef} onClick={popover.toggle}>
|
|
40
|
+
* Open
|
|
41
|
+
* </button>
|
|
42
|
+
*
|
|
43
|
+
* {popover.isOpen && (
|
|
44
|
+
* <div ref={popover.popoverRef}>
|
|
45
|
+
* Content
|
|
46
|
+
* </div>
|
|
47
|
+
* )}
|
|
48
|
+
* </>
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function usePopover<TAnchor extends HTMLElement = HTMLElement, TPopover extends HTMLElement = HTMLElement>(): UsePopoverReturn<TAnchor, TPopover>;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { usePopover };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePopover.ts
|
|
4
|
+
/**
|
|
5
|
+
* `usePopover` is React hook that manages the open/close state and lifecycle of a popover anchored to a trigger element.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const popover = usePopover();
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <>
|
|
13
|
+
* <button ref={popover.anchorRef} onClick={popover.toggle}>
|
|
14
|
+
* Open
|
|
15
|
+
* </button>
|
|
16
|
+
*
|
|
17
|
+
* {popover.isOpen && (
|
|
18
|
+
* <div ref={popover.popoverRef}>
|
|
19
|
+
* Content
|
|
20
|
+
* </div>
|
|
21
|
+
* )}
|
|
22
|
+
* </>
|
|
23
|
+
* );
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function usePopover() {
|
|
27
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
28
|
+
const anchorRef = React.useRef(null);
|
|
29
|
+
const popoverRef = React.useRef(null);
|
|
30
|
+
const open = React.useCallback(() => {
|
|
31
|
+
setIsOpen(true);
|
|
32
|
+
}, []);
|
|
33
|
+
const close = React.useCallback(() => {
|
|
34
|
+
setIsOpen(false);
|
|
35
|
+
}, []);
|
|
36
|
+
const toggle = React.useCallback(() => {
|
|
37
|
+
setIsOpen((isOpen$1) => !isOpen$1);
|
|
38
|
+
}, []);
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
if (!isOpen) return;
|
|
41
|
+
const onClickOutside = (event) => {
|
|
42
|
+
const target = event.target;
|
|
43
|
+
if (popoverRef.current?.contains(target) || anchorRef.current?.contains(target)) return;
|
|
44
|
+
close();
|
|
45
|
+
};
|
|
46
|
+
const onKeyDown = (event) => {
|
|
47
|
+
if (event.key === "Escape") close();
|
|
48
|
+
};
|
|
49
|
+
document.addEventListener("mousedown", onClickOutside);
|
|
50
|
+
document.addEventListener("keydown", onKeyDown);
|
|
51
|
+
return () => {
|
|
52
|
+
document.removeEventListener("mousedown", onClickOutside);
|
|
53
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
54
|
+
};
|
|
55
|
+
}, [isOpen, close]);
|
|
56
|
+
return {
|
|
57
|
+
anchorRef,
|
|
58
|
+
close,
|
|
59
|
+
isOpen,
|
|
60
|
+
open,
|
|
61
|
+
popoverRef,
|
|
62
|
+
toggle
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { usePopover };
|