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