UNPKG

rooks

Version:

Collection of awesome react hooks

707 lines (701 loc) 22.1 kB
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 };