rooks
Version:
Collection of awesome react hooks
707 lines (701 loc) • 22.1 kB
JavaScript
import {
useBroadcastChannel
} from "./chunk-4BDODBWX.js";
// src/hooks/useSuspenseNavigatorUserAgentData.ts
var DEFAULT_HINTS = [
"architecture",
"bitness",
"formFactors",
"fullVersionList",
"model",
"platformVersion",
"uaFullVersion",
"wow64"
];
var cache = /* @__PURE__ */ new Map();
function createCacheKey(hints) {
return hints.sort().join(",");
}
function useSuspenseNavigatorUserAgentData(hints = DEFAULT_HINTS) {
if (typeof navigator === "undefined" || !navigator.userAgentData) {
throw new Error(
"Navigator User Agent Data API is not supported in this browser. This API is currently only available in Chromium-based browsers."
);
}
const cacheKey = createCacheKey(hints);
let entry = cache.get(cacheKey);
if (!entry) {
const promise = navigator.userAgentData.getHighEntropyValues(hints);
entry = {
promise,
status: "pending"
};
cache.set(cacheKey, entry);
promise.then((result) => {
entry.status = "resolved";
entry.result = result;
}).catch((error) => {
entry.status = "rejected";
entry.error = error instanceof Error ? error : new Error(String(error));
});
}
if (entry.status === "pending") {
throw entry.promise;
}
if (entry.status === "rejected") {
throw entry.error;
}
return entry.result;
}
// src/hooks/useSuspenseNavigatorBattery.ts
import { useState, useEffect } from "react";
var cacheEntry = null;
function useSuspenseNavigatorBattery() {
if (typeof navigator === "undefined" || !navigator.getBattery) {
throw new Error(
"Navigator Battery API is not supported in this browser. This API requires HTTPS and is not supported in all browsers."
);
}
const [batteryState, setBatteryState] = useState(null);
if (!cacheEntry) {
const promise = navigator.getBattery();
cacheEntry = {
promise,
status: "pending"
};
promise.then((result) => {
cacheEntry.status = "resolved";
cacheEntry.result = result;
}).catch((error) => {
cacheEntry.status = "rejected";
cacheEntry.error = error instanceof Error ? error : new Error(String(error));
});
}
if (cacheEntry.status === "pending") {
throw cacheEntry.promise;
}
if (cacheEntry.status === "rejected") {
throw cacheEntry.error;
}
const initialBattery = cacheEntry.result;
useEffect(() => {
if (!initialBattery) return;
setBatteryState({
charging: initialBattery.charging,
chargingTime: initialBattery.chargingTime,
dischargingTime: initialBattery.dischargingTime,
level: initialBattery.level,
addEventListener: initialBattery.addEventListener.bind(initialBattery),
removeEventListener: initialBattery.removeEventListener.bind(initialBattery)
});
const handleChargingChange = () => {
setBatteryState((prev) => prev ? {
...prev,
charging: initialBattery.charging
} : null);
};
const handleChargingTimeChange = () => {
setBatteryState((prev) => prev ? {
...prev,
chargingTime: initialBattery.chargingTime
} : null);
};
const handleDischargingTimeChange = () => {
setBatteryState((prev) => prev ? {
...prev,
dischargingTime: initialBattery.dischargingTime
} : null);
};
const handleLevelChange = () => {
setBatteryState((prev) => prev ? {
...prev,
level: initialBattery.level
} : null);
};
initialBattery.addEventListener("chargingchange", handleChargingChange);
initialBattery.addEventListener("chargingtimechange", handleChargingTimeChange);
initialBattery.addEventListener("dischargingtimechange", handleDischargingTimeChange);
initialBattery.addEventListener("levelchange", handleLevelChange);
return () => {
initialBattery.removeEventListener("chargingchange", handleChargingChange);
initialBattery.removeEventListener("chargingtimechange", handleChargingTimeChange);
initialBattery.removeEventListener("dischargingtimechange", handleDischargingTimeChange);
initialBattery.removeEventListener("levelchange", handleLevelChange);
};
}, [initialBattery]);
return batteryState || initialBattery;
}
// src/hooks/useSuspenseLocalStorageState.ts
import { useState as useState2, useEffect as useEffect2, useCallback, useMemo } from "react";
var cache2 = /* @__PURE__ */ new Map();
function getValueFromLocalStorage(key) {
if (typeof localStorage === "undefined") {
return null;
}
try {
return localStorage.getItem(key);
} catch (error) {
console.error(`Failed to get value from localStorage for key "${key}":`, error);
return null;
}
}
function saveValueToLocalStorage(key, value) {
if (typeof localStorage === "undefined") {
return;
}
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`Failed to save value to localStorage for key "${key}":`, error);
}
}
function removeValueFromLocalStorage(key) {
if (typeof localStorage === "undefined") {
return;
}
try {
localStorage.removeItem(key);
} catch (error) {
console.error(`Failed to remove value from localStorage for key "${key}":`, error);
}
}
function createInitializationPromise(key, initializer) {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
try {
const rawValue = getValueFromLocalStorage(key);
const initializedValue = initializer(rawValue);
resolve(initializedValue);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
}, 0);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
function useSuspenseLocalStorageState(key, initializer) {
let cacheEntry2 = cache2.get(key);
if (!cacheEntry2) {
const promise = createInitializationPromise(key, initializer);
cacheEntry2 = {
promise,
status: "pending"
};
cache2.set(key, cacheEntry2);
promise.then((result) => {
const entry = cache2.get(key);
if (entry) {
entry.status = "resolved";
entry.result = result;
}
}).catch((error) => {
const entry = cache2.get(key);
if (entry) {
entry.status = "rejected";
entry.error = error instanceof Error ? error : new Error(String(error));
}
});
}
if (cacheEntry2.status === "pending") {
throw cacheEntry2.promise;
}
if (cacheEntry2.status === "rejected") {
throw cacheEntry2.error;
}
const initialValue = cacheEntry2.result;
const [value, setValue] = useState2(initialValue);
const [isDeleted, setIsDeleted] = useState2(false);
const customEventTypeName = useMemo(() => {
return `rooks-${key}-localstorage-update`;
}, [key]);
useEffect2(() => {
if (!isDeleted) {
saveValueToLocalStorage(key, value);
}
}, [key, value, isDeleted]);
const handleStorageEvent = useCallback(
(event) => {
if (event.storageArea === localStorage && event.key === key) {
try {
if (event.newValue === null) {
const newValue = initializer(null);
setValue(newValue);
} else {
const newValue = initializer(event.newValue);
setValue(newValue);
}
} catch (error) {
console.error(`Failed to handle storage event for key "${key}":`, error);
}
}
},
[key, initializer]
);
const handleCustomEvent = useCallback(
(event) => {
try {
const { newValue } = event.detail;
setValue(newValue);
} catch (error) {
console.error(`Failed to handle custom event for key "${key}":`, error);
}
},
[key]
);
useEffect2(() => {
if (typeof window !== "undefined") {
window.addEventListener("storage", handleStorageEvent);
return () => {
window.removeEventListener("storage", handleStorageEvent);
};
}
}, [handleStorageEvent]);
useEffect2(() => {
if (typeof document !== "undefined") {
document.addEventListener(
customEventTypeName,
handleCustomEvent
);
return () => {
document.removeEventListener(
customEventTypeName,
handleCustomEvent
);
};
}
}, [customEventTypeName, handleCustomEvent]);
const broadcastValueWithinDocument = useCallback(
(newValue) => {
if (typeof document !== "undefined") {
const event = new CustomEvent(
customEventTypeName,
{ detail: { newValue } }
);
document.dispatchEvent(event);
}
},
[customEventTypeName]
);
const controls = useMemo(() => ({
getItem: () => value,
setItem: (newValue) => {
setIsDeleted(false);
setValue(newValue);
broadcastValueWithinDocument(newValue);
},
deleteItem: () => {
removeValueFromLocalStorage(key);
const resetValue = initializer(null);
setIsDeleted(true);
setValue(resetValue);
broadcastValueWithinDocument(resetValue);
}
}), [value, key, initializer, broadcastValueWithinDocument]);
return [value, controls];
}
// src/hooks/useSuspenseSessionStorageState.ts
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useMemo as useMemo2 } from "react";
var cache3 = /* @__PURE__ */ new Map();
function getValueFromSessionStorage(key) {
if (typeof sessionStorage === "undefined") {
return null;
}
try {
return sessionStorage.getItem(key);
} catch (error) {
console.error(`Failed to get value from sessionStorage for key "${key}":`, error);
return null;
}
}
function saveValueToSessionStorage(key, value) {
if (typeof sessionStorage === "undefined") {
return;
}
try {
sessionStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`Failed to save value to sessionStorage for key "${key}":`, error);
}
}
function removeValueFromSessionStorage(key) {
if (typeof sessionStorage === "undefined") {
return;
}
try {
sessionStorage.removeItem(key);
} catch (error) {
console.error(`Failed to remove value from sessionStorage for key "${key}":`, error);
}
}
function createInitializationPromise2(key, initializer) {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
try {
const rawValue = getValueFromSessionStorage(key);
const initializedValue = initializer(rawValue);
resolve(initializedValue);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
}, 0);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
function useSuspenseSessionStorageState(key, initializer) {
let cacheEntry2 = cache3.get(key);
if (!cacheEntry2) {
const promise = createInitializationPromise2(key, initializer);
cacheEntry2 = {
promise,
status: "pending"
};
cache3.set(key, cacheEntry2);
promise.then((result) => {
const entry = cache3.get(key);
if (entry) {
entry.status = "resolved";
entry.result = result;
}
}).catch((error) => {
const entry = cache3.get(key);
if (entry) {
entry.status = "rejected";
entry.error = error instanceof Error ? error : new Error(String(error));
}
});
}
if (cacheEntry2.status === "pending") {
throw cacheEntry2.promise;
}
if (cacheEntry2.status === "rejected") {
throw cacheEntry2.error;
}
const initialValue = cacheEntry2.result;
const [value, setValue] = useState3(initialValue);
const [isDeleted, setIsDeleted] = useState3(false);
const customEventTypeName = useMemo2(() => {
return `rooks-${key}-sessionstorage-update`;
}, [key]);
useEffect3(() => {
if (!isDeleted) {
saveValueToSessionStorage(key, value);
}
}, [key, value, isDeleted]);
const handleStorageEvent = useCallback2(
(event) => {
if (event.storageArea === sessionStorage && event.key === key) {
try {
if (event.newValue === null) {
const newValue = initializer(null);
setValue(newValue);
} else {
const newValue = initializer(event.newValue);
setValue(newValue);
}
} catch (error) {
console.error(`Failed to handle storage event for key "${key}":`, error);
}
}
},
[key, initializer]
);
const handleCustomEvent = useCallback2(
(event) => {
try {
const { newValue } = event.detail;
setValue(newValue);
} catch (error) {
console.error(`Failed to handle custom event for key "${key}":`, error);
}
},
[key]
);
useEffect3(() => {
if (typeof window !== "undefined") {
window.addEventListener("storage", handleStorageEvent);
return () => {
window.removeEventListener("storage", handleStorageEvent);
};
}
}, [handleStorageEvent]);
useEffect3(() => {
if (typeof document !== "undefined") {
document.addEventListener(
customEventTypeName,
handleCustomEvent
);
return () => {
document.removeEventListener(
customEventTypeName,
handleCustomEvent
);
};
}
}, [customEventTypeName, handleCustomEvent]);
const broadcastValueWithinDocument = useCallback2(
(newValue) => {
if (typeof document !== "undefined") {
const event = new CustomEvent(
customEventTypeName,
{ detail: { newValue } }
);
document.dispatchEvent(event);
}
},
[customEventTypeName]
);
const controls = useMemo2(() => ({
getItem: () => value,
setItem: (newValue) => {
setIsDeleted(false);
setValue(newValue);
broadcastValueWithinDocument(newValue);
},
deleteItem: () => {
removeValueFromSessionStorage(key);
const resetValue = initializer(null);
setIsDeleted(true);
setValue(resetValue);
broadcastValueWithinDocument(resetValue);
}
}), [value, key, initializer, broadcastValueWithinDocument]);
return [value, controls];
}
// src/hooks/useSuspenseIndexedDBState.ts
import { useState as useState4, useCallback as useCallback3, useMemo as useMemo3, useRef } from "react";
var cache4 = /* @__PURE__ */ new Map();
function isIndexedDBSupported() {
return typeof indexedDB !== "undefined";
}
function openDatabase(dbName, version, storeName) {
return new Promise((resolve, reject) => {
if (!isIndexedDBSupported()) {
reject(new Error("IndexedDB is not supported in this environment"));
return;
}
const request = indexedDB.open(dbName, version);
request.onerror = () => {
reject(new Error(`Failed to open database "${dbName}": ${request.error?.message}`));
};
request.onsuccess = () => {
resolve(request.result);
};
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName);
}
};
});
}
async function getValueFromIndexedDB(dbName, storeName, key, version) {
try {
const db = await openDatabase(dbName, version, storeName);
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], "readonly");
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onerror = () => {
reject(new Error(`Failed to get value for key "${key}": ${request.error?.message}`));
};
request.onsuccess = () => {
resolve(request.result ?? null);
};
transaction.oncomplete = () => {
db.close();
};
});
} catch (error) {
console.error(`Failed to get value from IndexedDB for key "${key}":`, error);
return null;
}
}
async function saveValueToIndexedDB(dbName, storeName, key, value, version) {
try {
const db = await openDatabase(dbName, version, storeName);
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const request = store.put(value, key);
request.onerror = () => {
reject(new Error(`Failed to save value for key "${key}": ${request.error?.message}`));
};
request.onsuccess = () => {
resolve();
};
transaction.oncomplete = () => {
db.close();
};
});
} catch (error) {
console.error(`Failed to save value to IndexedDB for key "${key}":`, error);
throw error;
}
}
async function removeValueFromIndexedDB(dbName, storeName, key, version) {
try {
const db = await openDatabase(dbName, version, storeName);
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onerror = () => {
reject(new Error(`Failed to remove value for key "${key}": ${request.error?.message}`));
};
request.onsuccess = () => {
resolve();
};
transaction.oncomplete = () => {
db.close();
};
});
} catch (error) {
console.error(`Failed to remove value from IndexedDB for key "${key}":`, error);
throw error;
}
}
function createInitializationPromise3(dbName, storeName, key, version, initializer) {
return new Promise((resolve, reject) => {
getValueFromIndexedDB(dbName, storeName, key, version).then((rawValue) => {
try {
const initializedValue = initializer(rawValue);
resolve(initializedValue);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
}).catch((error) => {
reject(error instanceof Error ? error : new Error(String(error)));
});
});
}
function useSuspenseIndexedDBState(key, initializer, config = {}) {
const {
dbName = "rooks-db",
storeName = "state",
version = 1
} = config;
const cacheKey = `${dbName}:${storeName}:${key}:${version}`;
let cacheEntry2 = cache4.get(cacheKey);
if (!cacheEntry2) {
const promise = createInitializationPromise3(dbName, storeName, key, version, initializer);
cacheEntry2 = {
promise,
status: "pending"
};
cache4.set(cacheKey, cacheEntry2);
promise.then((result) => {
const entry = cache4.get(cacheKey);
if (entry) {
entry.status = "resolved";
entry.result = result;
}
}).catch((error) => {
const entry = cache4.get(cacheKey);
if (entry) {
entry.status = "rejected";
entry.error = error instanceof Error ? error : new Error(String(error));
}
});
}
if (cacheEntry2.status === "pending") {
throw cacheEntry2.promise;
}
if (cacheEntry2.status === "rejected") {
throw cacheEntry2.error;
}
const initialValue = cacheEntry2.result;
const [value, setValue] = useState4(initialValue);
const channelName = useMemo3(() => {
return `rooks-indexeddb-${dbName}-${storeName}`;
}, [dbName, storeName]);
const initializerRef = useRef(initializer);
initializerRef.current = initializer;
const handleBroadcastMessage = useCallback3(
(data) => {
const { type, key: messageKey, value: messageValue, dbName: msgDbName, storeName: msgStoreName } = data;
if (msgDbName === dbName && msgStoreName === storeName && messageKey === key) {
try {
if (type === "SET" && messageValue !== void 0) {
setValue(messageValue);
const entry = cache4.get(cacheKey);
if (entry && entry.status === "resolved") {
entry.result = messageValue;
}
} else if (type === "DELETE") {
const resetValue = initializerRef.current(null);
setValue(resetValue);
const entry = cache4.get(cacheKey);
if (entry && entry.status === "resolved") {
entry.result = resetValue;
}
}
} catch (error) {
console.error(`Failed to handle broadcast message for key "${key}":`, error);
}
}
},
[dbName, storeName, key, cacheKey]
);
const { postMessage: broadcastMessage, isSupported: isBroadcastSupported } = useBroadcastChannel(
channelName,
{
onMessage: handleBroadcastMessage,
onError: (error) => {
console.error(`BroadcastChannel error for key "${key}":`, error);
}
}
);
const broadcastChange = useCallback3(
(type, newValue) => {
if (isBroadcastSupported) {
const message = {
type,
key,
value: newValue,
dbName,
storeName
};
broadcastMessage(message);
}
},
[broadcastMessage, isBroadcastSupported, key, dbName, storeName]
);
const controls = useMemo3(() => ({
getItem: () => value,
setItem: async (newValue) => {
try {
await saveValueToIndexedDB(dbName, storeName, key, newValue, version);
setValue(newValue);
broadcastChange("SET", newValue);
} catch (error) {
console.error(`Failed to set item in IndexedDB for key "${key}":`, error);
throw error;
}
},
deleteItem: async () => {
try {
await removeValueFromIndexedDB(dbName, storeName, key, version);
const resetValue = initializerRef.current(null);
setValue(resetValue);
broadcastChange("DELETE");
} catch (error) {
console.error(`Failed to delete item from IndexedDB for key "${key}":`, error);
throw error;
}
}
}), [value, dbName, storeName, key, version, broadcastChange]);
return [value, controls];
}
export {
useSuspenseIndexedDBState,
useSuspenseLocalStorageState,
useSuspenseNavigatorBattery,
useSuspenseNavigatorUserAgentData,
useSuspenseSessionStorageState
};