@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,55 @@
|
|
|
1
|
+
//#region src/usePreferredLanguage.d.ts
|
|
2
|
+
|
|
3
|
+
interface PreferredLanguageReturn {
|
|
4
|
+
/**
|
|
5
|
+
* Language effectively used by the application.
|
|
6
|
+
*/
|
|
7
|
+
language: string;
|
|
8
|
+
/**
|
|
9
|
+
* Updates the user-selected language.
|
|
10
|
+
*/
|
|
11
|
+
setUserLanguage: (lang: string) => void;
|
|
12
|
+
/**
|
|
13
|
+
* System / browser language.
|
|
14
|
+
*/
|
|
15
|
+
systemLanguage: string;
|
|
16
|
+
/**
|
|
17
|
+
* User-selected language, if any.
|
|
18
|
+
*/
|
|
19
|
+
userLanguage: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `usePreferredLanguage` is a custom React hook that resolves the effective language for the application.
|
|
23
|
+
* This hook is SSR-safe and unopinionated.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* function LanguageSelector() {
|
|
28
|
+
* const {
|
|
29
|
+
* language,
|
|
30
|
+
* userLanguage,
|
|
31
|
+
* systemLanguage,
|
|
32
|
+
* setUserLanguage,
|
|
33
|
+
* } = useResolvedLanguage();
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <div>
|
|
37
|
+
* <p>Resolved: {language}</p>
|
|
38
|
+
* <p>System: {systemLanguage}</p>
|
|
39
|
+
* <p>User: {userLanguage ?? 'system default'}</p>
|
|
40
|
+
*
|
|
41
|
+
* <button onClick={() => setUserLanguage('en')}>
|
|
42
|
+
* English
|
|
43
|
+
* </button>
|
|
44
|
+
*
|
|
45
|
+
* <button onClick={() => setUserLanguage('es-PE')}>
|
|
46
|
+
* Español
|
|
47
|
+
* </button>
|
|
48
|
+
* </div>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function usePreferredLanguage(): PreferredLanguageReturn;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { usePreferredLanguage };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePreferredLanguage.ts
|
|
4
|
+
function createSystemLanguageStore() {
|
|
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 window !== "undefined") {
|
|
13
|
+
window.addEventListener("languagechange", emit);
|
|
14
|
+
listening = true;
|
|
15
|
+
}
|
|
16
|
+
return () => {
|
|
17
|
+
listeners.delete(listener);
|
|
18
|
+
if (listeners.size === 0 && listening) {
|
|
19
|
+
window.removeEventListener("languagechange", emit);
|
|
20
|
+
listening = false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function getSnapshot() {
|
|
25
|
+
if (typeof window === "undefined") return "en";
|
|
26
|
+
return window.navigator.language;
|
|
27
|
+
}
|
|
28
|
+
function getServerSnapshot() {
|
|
29
|
+
return "en";
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
getServerSnapshot,
|
|
33
|
+
getSnapshot,
|
|
34
|
+
subscribe
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const systemLanguageStore = createSystemLanguageStore();
|
|
38
|
+
function createUserLanguageStore() {
|
|
39
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
40
|
+
const storageKey = "preferred-language";
|
|
41
|
+
let listening = false;
|
|
42
|
+
function emit() {
|
|
43
|
+
listeners.forEach((listener) => listener());
|
|
44
|
+
}
|
|
45
|
+
function subscribe(listener) {
|
|
46
|
+
listeners.add(listener);
|
|
47
|
+
if (!listening && typeof window !== "undefined") {
|
|
48
|
+
const onStorage = (event) => {
|
|
49
|
+
if (event.key === storageKey) emit();
|
|
50
|
+
};
|
|
51
|
+
window.addEventListener("storage", onStorage);
|
|
52
|
+
listening = true;
|
|
53
|
+
return () => {
|
|
54
|
+
listeners.delete(listener);
|
|
55
|
+
if (listeners.size === 0) {
|
|
56
|
+
window.removeEventListener("storage", onStorage);
|
|
57
|
+
listening = false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return () => listeners.delete(listener);
|
|
62
|
+
}
|
|
63
|
+
function getSnapshot() {
|
|
64
|
+
if (typeof window === "undefined") return "en";
|
|
65
|
+
return localStorage.getItem(storageKey) ?? "en";
|
|
66
|
+
}
|
|
67
|
+
function getServerSnapshot() {
|
|
68
|
+
return "en";
|
|
69
|
+
}
|
|
70
|
+
function setLanguage(lang) {
|
|
71
|
+
if (typeof window === "undefined") return;
|
|
72
|
+
localStorage.setItem(storageKey, lang);
|
|
73
|
+
emit();
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
getServerSnapshot,
|
|
77
|
+
getSnapshot,
|
|
78
|
+
setLanguage,
|
|
79
|
+
subscribe
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const userLanguageStore = createUserLanguageStore();
|
|
83
|
+
/**
|
|
84
|
+
* `usePreferredLanguage` is a custom React hook that resolves the effective language for the application.
|
|
85
|
+
* This hook is SSR-safe and unopinionated.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* function LanguageSelector() {
|
|
90
|
+
* const {
|
|
91
|
+
* language,
|
|
92
|
+
* userLanguage,
|
|
93
|
+
* systemLanguage,
|
|
94
|
+
* setUserLanguage,
|
|
95
|
+
* } = useResolvedLanguage();
|
|
96
|
+
*
|
|
97
|
+
* return (
|
|
98
|
+
* <div>
|
|
99
|
+
* <p>Resolved: {language}</p>
|
|
100
|
+
* <p>System: {systemLanguage}</p>
|
|
101
|
+
* <p>User: {userLanguage ?? 'system default'}</p>
|
|
102
|
+
*
|
|
103
|
+
* <button onClick={() => setUserLanguage('en')}>
|
|
104
|
+
* English
|
|
105
|
+
* </button>
|
|
106
|
+
*
|
|
107
|
+
* <button onClick={() => setUserLanguage('es-PE')}>
|
|
108
|
+
* Español
|
|
109
|
+
* </button>
|
|
110
|
+
* </div>
|
|
111
|
+
* );
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
function usePreferredLanguage() {
|
|
116
|
+
const systemLanguage = React.useSyncExternalStore(systemLanguageStore.subscribe, systemLanguageStore.getSnapshot, systemLanguageStore.getServerSnapshot);
|
|
117
|
+
const userLanguage = React.useSyncExternalStore(userLanguageStore.subscribe, userLanguageStore.getSnapshot, userLanguageStore.getServerSnapshot);
|
|
118
|
+
return {
|
|
119
|
+
language: userLanguage || systemLanguage || "en",
|
|
120
|
+
setUserLanguage: userLanguageStore.setLanguage,
|
|
121
|
+
systemLanguage,
|
|
122
|
+
userLanguage: userLanguage ?? null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { usePreferredLanguage };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/usePreferredTheme.d.ts
|
|
2
|
+
type Theme = string;
|
|
3
|
+
interface PreferredThemeReturn {
|
|
4
|
+
/**
|
|
5
|
+
* Sets a user-selected theme.
|
|
6
|
+
*/
|
|
7
|
+
setUserTheme: (theme: Theme | null) => void;
|
|
8
|
+
/**
|
|
9
|
+
* System / OS preferred theme.
|
|
10
|
+
*/
|
|
11
|
+
systemTheme: Theme;
|
|
12
|
+
/**
|
|
13
|
+
* Theme effectively used by the application.
|
|
14
|
+
*/
|
|
15
|
+
theme: Theme;
|
|
16
|
+
/**
|
|
17
|
+
* Toggles between two themes.
|
|
18
|
+
* Defaults to 'light' and 'dark'.
|
|
19
|
+
*/
|
|
20
|
+
toggleTheme: (options?: {
|
|
21
|
+
dark?: Theme;
|
|
22
|
+
light?: Theme;
|
|
23
|
+
}) => void;
|
|
24
|
+
/**
|
|
25
|
+
* User-selected theme, if any.
|
|
26
|
+
*/
|
|
27
|
+
userTheme: Theme | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `usePreferredTheme` resolves the effective theme for the application.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function ThemeSwitcher() {
|
|
35
|
+
* const {
|
|
36
|
+
* theme,
|
|
37
|
+
* systemTheme,
|
|
38
|
+
* userTheme,
|
|
39
|
+
* toggleTheme,
|
|
40
|
+
* setUserTheme
|
|
41
|
+
* } = usePreferredTheme();
|
|
42
|
+
*
|
|
43
|
+
* return (
|
|
44
|
+
* <div>
|
|
45
|
+
* <p>Resolved: {theme}</p>
|
|
46
|
+
* <p>System: {systemTheme}</p>
|
|
47
|
+
* <p>User: {userTheme ?? 'system default'}</p>
|
|
48
|
+
*
|
|
49
|
+
* <button onClick={() => toggleTheme()}>
|
|
50
|
+
* Toggle
|
|
51
|
+
* </button>
|
|
52
|
+
*
|
|
53
|
+
* <button onClick={() => setUserTheme('dark')}>
|
|
54
|
+
* Force Dark
|
|
55
|
+
* </button>
|
|
56
|
+
*
|
|
57
|
+
* <button onClick={() => setUserTheme(null)}>
|
|
58
|
+
* Reset to System
|
|
59
|
+
* </button>
|
|
60
|
+
* </div>
|
|
61
|
+
* );
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function usePreferredTheme(): PreferredThemeReturn;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { usePreferredTheme };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePreferredTheme.ts
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
function emit() {
|
|
6
|
+
listeners.forEach((listener) => listener());
|
|
7
|
+
}
|
|
8
|
+
let media = null;
|
|
9
|
+
let mediaListenerAttached = false;
|
|
10
|
+
const systemThemeStore = {
|
|
11
|
+
getServerSnapshot() {
|
|
12
|
+
return "light";
|
|
13
|
+
},
|
|
14
|
+
getSnapshot() {
|
|
15
|
+
if (typeof window === "undefined") return "light";
|
|
16
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
17
|
+
},
|
|
18
|
+
suscribe(listener) {
|
|
19
|
+
listeners.add(listener);
|
|
20
|
+
if (typeof window !== "undefined") {
|
|
21
|
+
if (!media) media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
22
|
+
if (!mediaListenerAttached) {
|
|
23
|
+
media.addEventListener("change", emit);
|
|
24
|
+
mediaListenerAttached = true;
|
|
25
|
+
}
|
|
26
|
+
return () => {
|
|
27
|
+
listeners.delete(listener);
|
|
28
|
+
if (listeners.size === 0 && media && mediaListenerAttached) {
|
|
29
|
+
media.removeEventListener("change", emit);
|
|
30
|
+
mediaListenerAttached = false;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return () => listeners.delete(listener);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const storageKey = "preferred-theme";
|
|
38
|
+
const userThemeStore = {
|
|
39
|
+
getSeverSnapshot() {
|
|
40
|
+
return null;
|
|
41
|
+
},
|
|
42
|
+
getSnapshot() {
|
|
43
|
+
if (typeof window === "undefined") return null;
|
|
44
|
+
return localStorage.getItem(storageKey) ?? null;
|
|
45
|
+
},
|
|
46
|
+
setTheme(theme) {
|
|
47
|
+
if (typeof window === "undefined") return;
|
|
48
|
+
if (theme === null) localStorage.removeItem(storageKey);
|
|
49
|
+
else localStorage.setItem(storageKey, theme);
|
|
50
|
+
emit();
|
|
51
|
+
},
|
|
52
|
+
suscribe(listener) {
|
|
53
|
+
listeners.add(listener);
|
|
54
|
+
if (typeof window !== "undefined") {
|
|
55
|
+
const onStorage = (event) => {
|
|
56
|
+
if (event.key === storageKey) emit();
|
|
57
|
+
};
|
|
58
|
+
window.addEventListener("storage", onStorage);
|
|
59
|
+
return () => {
|
|
60
|
+
listeners.delete(listener);
|
|
61
|
+
window.removeEventListener("storage", onStorage);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return () => listeners.delete(listener);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function updateDOMTheme(theme) {
|
|
68
|
+
if (typeof window === "undefined") return;
|
|
69
|
+
const root = document.documentElement;
|
|
70
|
+
if (theme === "dark") root.classList.add("dark");
|
|
71
|
+
else root.classList.remove("dark");
|
|
72
|
+
root.style.colorScheme = theme;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `usePreferredTheme` resolves the effective theme for the application.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* function ThemeSwitcher() {
|
|
80
|
+
* const {
|
|
81
|
+
* theme,
|
|
82
|
+
* systemTheme,
|
|
83
|
+
* userTheme,
|
|
84
|
+
* toggleTheme,
|
|
85
|
+
* setUserTheme
|
|
86
|
+
* } = usePreferredTheme();
|
|
87
|
+
*
|
|
88
|
+
* return (
|
|
89
|
+
* <div>
|
|
90
|
+
* <p>Resolved: {theme}</p>
|
|
91
|
+
* <p>System: {systemTheme}</p>
|
|
92
|
+
* <p>User: {userTheme ?? 'system default'}</p>
|
|
93
|
+
*
|
|
94
|
+
* <button onClick={() => toggleTheme()}>
|
|
95
|
+
* Toggle
|
|
96
|
+
* </button>
|
|
97
|
+
*
|
|
98
|
+
* <button onClick={() => setUserTheme('dark')}>
|
|
99
|
+
* Force Dark
|
|
100
|
+
* </button>
|
|
101
|
+
*
|
|
102
|
+
* <button onClick={() => setUserTheme(null)}>
|
|
103
|
+
* Reset to System
|
|
104
|
+
* </button>
|
|
105
|
+
* </div>
|
|
106
|
+
* );
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
function usePreferredTheme() {
|
|
111
|
+
const systemTheme = React.useSyncExternalStore(systemThemeStore.suscribe, systemThemeStore.getSnapshot, systemThemeStore.getServerSnapshot);
|
|
112
|
+
const userTheme = React.useSyncExternalStore(userThemeStore.suscribe, userThemeStore.getSnapshot, userThemeStore.getSeverSnapshot);
|
|
113
|
+
const resolved = userTheme ?? systemTheme ?? "light";
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
updateDOMTheme(resolved);
|
|
116
|
+
}, [resolved]);
|
|
117
|
+
const toggleTheme = React.useCallback((options) => {
|
|
118
|
+
const light = options?.light ?? "light";
|
|
119
|
+
const dark = options?.dark ?? "dark";
|
|
120
|
+
const next = resolved === "dark" ? light : dark;
|
|
121
|
+
userThemeStore.setTheme(next);
|
|
122
|
+
}, [resolved]);
|
|
123
|
+
return {
|
|
124
|
+
setUserTheme: userThemeStore.setTheme,
|
|
125
|
+
systemTheme,
|
|
126
|
+
theme: resolved,
|
|
127
|
+
toggleTheme,
|
|
128
|
+
userTheme: userTheme ?? null
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { usePreferredTheme };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/usePreviousDistinct.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `usePreviousDistinct` is a React hook that returns the previous value of a state variable, but only if it is different from the current value.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const prevUserId = usePreviousDistinct(userId);
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
declare function usePreviousDistinct<T>(value: T): T | undefined;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { usePreviousDistinct };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/usePreviousDistinct.ts
|
|
4
|
+
/**
|
|
5
|
+
* `usePreviousDistinct` is a React hook that returns the previous value of a state variable, but only if it is different from the current value.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const prevUserId = usePreviousDistinct(userId);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
function usePreviousDistinct(value) {
|
|
13
|
+
const prevRef = React.useRef(void 0);
|
|
14
|
+
const currentRef = React.useRef(void 0);
|
|
15
|
+
if (!Object.is(value, currentRef.current)) {
|
|
16
|
+
prevRef.current = currentRef.current;
|
|
17
|
+
currentRef.current = value;
|
|
18
|
+
}
|
|
19
|
+
return prevRef.current;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { usePreviousDistinct };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useResettableState.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `useResettableState` is a React state hook with an explicit reset mechanism.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const [count, setCount, reset] = useResettableState(0);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare function useResettableState<T>(initialState: T | (() => T)): readonly [T, React.Dispatch<React.SetStateAction<T>>, () => void];
|
|
14
|
+
//#endregion
|
|
15
|
+
export { useResettableState };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useResettableState.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useResettableState` is a React state hook with an explicit reset mechanism.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const [count, setCount, reset] = useResettableState(0);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
function useResettableState(initialState) {
|
|
13
|
+
const initialRef = React.useRef(initialState instanceof Function ? initialState() : initialState);
|
|
14
|
+
const [state, setState] = React.useState(initialState);
|
|
15
|
+
return [
|
|
16
|
+
state,
|
|
17
|
+
setState,
|
|
18
|
+
React.useCallback(() => {
|
|
19
|
+
setState(typeof initialRef.current === "function" ? initialRef.current() : initialRef.current);
|
|
20
|
+
}, [])
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { useResettableState };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/useScreenOrientation.d.ts
|
|
2
|
+
declare global {
|
|
3
|
+
interface ScreenOrientation {
|
|
4
|
+
lock(orientation: ScreenOrientationLock): Promise<void>;
|
|
5
|
+
unlock(): void;
|
|
6
|
+
}
|
|
7
|
+
type ScreenOrientationLock = 'any' | 'natural' | 'portrait' | 'landscape' | 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary';
|
|
8
|
+
}
|
|
9
|
+
type OrientationType = 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary';
|
|
10
|
+
interface UseScreenOrientationReturn {
|
|
11
|
+
/**
|
|
12
|
+
* Current orientation angle in degrees.
|
|
13
|
+
*/
|
|
14
|
+
angle: number | null;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the Screen Orientation API is supported.
|
|
17
|
+
*/
|
|
18
|
+
isSupported: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Locks the screen orientation.
|
|
21
|
+
*/
|
|
22
|
+
lock: (orientation: ScreenOrientationLock) => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Current orientation type (e.g. portrait-primary).
|
|
25
|
+
*/
|
|
26
|
+
type: OrientationType | null;
|
|
27
|
+
/**
|
|
28
|
+
* Unlocks the screen orientation.
|
|
29
|
+
*/
|
|
30
|
+
unlock: () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `useScreenOrientation` provides unopinionated access to the Screen Orientation API.
|
|
34
|
+
* It exposes current orientation state and helpers to lock/unlock orientation without imposing UI decisiones.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const orientation = useScreenOrientation();
|
|
39
|
+
*
|
|
40
|
+
* if (orientation.type === 'landscape-primary') {
|
|
41
|
+
* // adapt layout
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Screen_Orientation_API
|
|
45
|
+
*/
|
|
46
|
+
declare function useScreenOrientation(): UseScreenOrientationReturn;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { useScreenOrientation };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useScreenOrientation.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useScreenOrientation` provides unopinionated access to the Screen Orientation API.
|
|
6
|
+
* It exposes current orientation state and helpers to lock/unlock orientation without imposing UI decisiones.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const orientation = useScreenOrientation();
|
|
11
|
+
*
|
|
12
|
+
* if (orientation.type === 'landscape-primary') {
|
|
13
|
+
* // adapt layout
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Screen_Orientation_API
|
|
17
|
+
*/
|
|
18
|
+
function useScreenOrientation() {
|
|
19
|
+
const isSupported = typeof screen !== "undefined" && screen.orientation !== void 0;
|
|
20
|
+
const [type, setType] = React.useState(isSupported ? screen.orientation.type : null);
|
|
21
|
+
const [angle, setAngle] = React.useState(isSupported ? screen.orientation.angle : null);
|
|
22
|
+
const lock = React.useCallback(async (orientation) => {
|
|
23
|
+
if (!isSupported) return;
|
|
24
|
+
await screen.orientation.lock(orientation);
|
|
25
|
+
}, [isSupported]);
|
|
26
|
+
const unlock = React.useCallback(() => {
|
|
27
|
+
if (!isSupported) return;
|
|
28
|
+
screen.orientation.unlock();
|
|
29
|
+
}, [isSupported]);
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (!isSupported) return;
|
|
32
|
+
const handleChange = () => {
|
|
33
|
+
setType(screen.orientation.type);
|
|
34
|
+
setAngle(screen.orientation.angle);
|
|
35
|
+
};
|
|
36
|
+
screen.orientation.addEventListener("change", handleChange);
|
|
37
|
+
return () => {
|
|
38
|
+
screen.orientation.removeEventListener("change", handleChange);
|
|
39
|
+
};
|
|
40
|
+
}, [isSupported]);
|
|
41
|
+
return {
|
|
42
|
+
angle,
|
|
43
|
+
isSupported,
|
|
44
|
+
lock,
|
|
45
|
+
type,
|
|
46
|
+
unlock
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { useScreenOrientation };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/useScreenSize.d.ts
|
|
2
|
+
interface ScreenSizeReturn {
|
|
3
|
+
height: number;
|
|
4
|
+
width: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* `useScreenSize` returns the current screen size.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const { width, height } = useScreenSize();
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
declare function useScreenSize(): ScreenSizeReturn;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { useScreenSize };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useScreenSize.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useScreenSize` returns the current screen size.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const { width, height } = useScreenSize();
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
function useScreenSize() {
|
|
13
|
+
const [size, setSize] = React.useState({
|
|
14
|
+
height: 0,
|
|
15
|
+
width: 0
|
|
16
|
+
});
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const handleResize = () => {
|
|
19
|
+
setSize({
|
|
20
|
+
height: window.innerHeight,
|
|
21
|
+
width: window.innerWidth
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
handleResize();
|
|
25
|
+
window.addEventListener("resize", handleResize);
|
|
26
|
+
return () => {
|
|
27
|
+
window.removeEventListener("resize", handleResize);
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
return size;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { useScreenSize };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/useScreenWakeLock.d.ts
|
|
2
|
+
interface UseScreenWakeLockReturn {
|
|
3
|
+
/**
|
|
4
|
+
* Whether the wake lock is currently active.
|
|
5
|
+
*/
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the Wake Lock API is supported.
|
|
9
|
+
*/
|
|
10
|
+
isSupported: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Releases the wake lock.
|
|
13
|
+
*/
|
|
14
|
+
release: () => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Requests a screen wake lock.
|
|
17
|
+
*/
|
|
18
|
+
request: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `useScreenWakeLock` provides unopinionated access to the Screen Wake Lock Web API.
|
|
22
|
+
* It allows consumers to request and release a screen wake lock without imposing lifecycle or UI behavior.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const wakeLock = useScreenWakeLock();
|
|
27
|
+
*
|
|
28
|
+
* useEffect(() => {
|
|
29
|
+
* wakeLock.request();
|
|
30
|
+
* return () => wakeLock.release();
|
|
31
|
+
* }, []);
|
|
32
|
+
* ```
|
|
33
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
|
|
34
|
+
*/
|
|
35
|
+
declare function useScreenWakeLock(): UseScreenWakeLockReturn;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { useScreenWakeLock };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useScreenWakeLock.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useScreenWakeLock` provides unopinionated access to the Screen Wake Lock Web API.
|
|
6
|
+
* It allows consumers to request and release a screen wake lock without imposing lifecycle or UI behavior.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const wakeLock = useScreenWakeLock();
|
|
11
|
+
*
|
|
12
|
+
* useEffect(() => {
|
|
13
|
+
* wakeLock.request();
|
|
14
|
+
* return () => wakeLock.release();
|
|
15
|
+
* }, []);
|
|
16
|
+
* ```
|
|
17
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
|
|
18
|
+
*/
|
|
19
|
+
function useScreenWakeLock() {
|
|
20
|
+
const isSupported = typeof navigator !== "undefined" && "wakeLock" in navigator;
|
|
21
|
+
const lockRef = React.useRef(null);
|
|
22
|
+
const [isActive, setIsActive] = React.useState(false);
|
|
23
|
+
const request = React.useCallback(async () => {
|
|
24
|
+
if (!isSupported || lockRef.current) return;
|
|
25
|
+
try {
|
|
26
|
+
lockRef.current = await navigator.wakeLock.request("screen");
|
|
27
|
+
setIsActive(true);
|
|
28
|
+
lockRef.current.addEventListener("release", () => {
|
|
29
|
+
lockRef.current = null;
|
|
30
|
+
setIsActive(false);
|
|
31
|
+
});
|
|
32
|
+
} catch {}
|
|
33
|
+
}, [isSupported]);
|
|
34
|
+
return {
|
|
35
|
+
isActive,
|
|
36
|
+
isSupported,
|
|
37
|
+
release: React.useCallback(async () => {
|
|
38
|
+
if (!lockRef.current) return;
|
|
39
|
+
await lockRef.current.release();
|
|
40
|
+
lockRef.current = null;
|
|
41
|
+
setIsActive(false);
|
|
42
|
+
}, []),
|
|
43
|
+
request
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { useScreenWakeLock };
|