UNPKG

rooks

Version:

Collection of awesome react hooks

1,006 lines (999 loc) 31.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/useSuspenseFavicon.ts import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react"; var MANAGED_FAVICON_ATTRIBUTE = "data-rooks-managed-favicon"; var ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/; var resource = { status: "idle", currentValue: null, subscribers: /* @__PURE__ */ new Set(), originalLinks: null, managedLink: null, isManaging: false, instanceCount: 0, instanceStrategies: /* @__PURE__ */ new Map() }; function toError(error) { return error instanceof Error ? error : new Error(String(error)); } function ensureBrowserEnvironment() { if (typeof window === "undefined" || typeof document === "undefined") { throw new Error( "useSuspenseFavicon can only be used in a browser environment." ); } } function getDocumentHead() { ensureBrowserEnvironment(); if (!document.head) { throw new Error("useSuspenseFavicon requires document.head to be available."); } return document.head; } function isIconLink(link) { const relAttribute = link.getAttribute("rel"); if (!relAttribute) { return false; } return relAttribute.split(/\s+/).map((token) => token.trim().toLowerCase()).filter(Boolean).includes("icon"); } function getExplicitFaviconLinks() { const head = getDocumentHead(); return Array.from(head.querySelectorAll("link[rel][href]")).filter( (node) => node instanceof HTMLLinkElement && isIconLink(node) ); } function normalizeRelativeHref(url) { return `${url.pathname}${url.search}${url.hash}`; } function createSameOriginFavicon(url, relativeHref = normalizeRelativeHref(url)) { return { kind: "same-origin", relativeHref, href: url.href }; } function classifyFaviconHref(href, preferredRelativeHref) { const resolved = new URL(href, document.baseURI); if (resolved.origin === window.location.origin) { return createSameOriginFavicon(resolved, preferredRelativeHref); } return { kind: "external", url: resolved.href, href: resolved.href }; } function discoverCurrentFavicon() { ensureBrowserEnvironment(); const links = getExplicitFaviconLinks(); for (let index = links.length - 1; index >= 0; index -= 1) { const href = links[index]?.getAttribute("href"); if (!href) { continue; } try { return classifyFaviconHref(href); } catch { continue; } } return null; } function createInitializationPromise() { return new Promise((resolve, reject) => { setTimeout(() => { try { resolve(discoverCurrentFavicon()); } catch (error) { reject(toError(error)); } }, 0); }); } function initializeResourceIfNeeded() { ensureBrowserEnvironment(); if (resource.status !== "idle") { return; } const promise = createInitializationPromise(); resource.status = "pending"; resource.promise = promise; promise.then((result) => { resource.status = "resolved"; resource.result = result; resource.currentValue = result; }).catch((error) => { resource.status = "rejected"; resource.error = toError(error); }); } function notifySubscribers(nextValue) { resource.currentValue = nextValue; resource.result = nextValue; resource.status = "resolved"; resource.error = void 0; for (const subscriber of resource.subscribers) { subscriber(nextValue); } } function ensureManagedMode() { if (resource.isManaging) { return; } const faviconLinks = getExplicitFaviconLinks(); resource.originalLinks = faviconLinks.map((link) => { const placeholder = document.createComment("rooks-favicon-placeholder"); link.parentNode?.insertBefore(placeholder, link); link.remove(); return { link, placeholder }; }); resource.isManaging = true; resource.managedLink = null; } function getOrCreateManagedLink() { const head = getDocumentHead(); if (resource.managedLink?.isConnected) { return resource.managedLink; } const link = document.createElement("link"); link.setAttribute("rel", "icon"); link.setAttribute(MANAGED_FAVICON_ATTRIBUTE, "true"); head.appendChild(link); resource.managedLink = link; return link; } function cleanupManagedLinkReference() { if (resource.managedLink?.isConnected) { resource.managedLink.remove(); } resource.managedLink = null; } function restoreOriginalLinks() { const head = typeof document !== "undefined" ? document.head : null; cleanupManagedLinkReference(); if (!resource.originalLinks) { resource.isManaging = false; return; } for (const { link, placeholder } of resource.originalLinks) { if (placeholder.parentNode) { placeholder.parentNode.replaceChild(link, placeholder); } else if (head) { head.appendChild(link); } } resource.originalLinks = null; resource.isManaging = false; } function discardOriginalSnapshot() { if (resource.originalLinks) { for (const { placeholder } of resource.originalLinks) { placeholder.remove(); } } resource.originalLinks = null; resource.isManaging = false; resource.managedLink = null; } function resetInitializationState() { resource.status = "idle"; resource.promise = void 0; resource.result = void 0; resource.error = void 0; resource.currentValue = null; } function cleanupAfterLastUnmount(strategy) { if (resource.isManaging || resource.originalLinks || resource.managedLink) { if (strategy === "restore-originals") { restoreOriginalLinks(); } else { discardOriginalSnapshot(); } } resource.instanceStrategies.clear(); resource.instanceCount = 0; resetInitializationState(); } function resolveSameOriginRelativeHref(relativeHref) { ensureBrowserEnvironment(); const trimmedRelativeHref = relativeHref.trim(); if (!trimmedRelativeHref) { throw new Error( "useSuspenseFavicon expected a non-empty same-origin relativeHref." ); } if (ABSOLUTE_URL_PATTERN.test(trimmedRelativeHref) || trimmedRelativeHref.startsWith("//")) { throw new Error( "useSuspenseFavicon expected same-origin updates to use a relative href string." ); } const resolved = new URL(trimmedRelativeHref, document.baseURI); if (resolved.origin !== window.location.origin) { throw new Error( "useSuspenseFavicon expected same-origin updates to resolve to the current origin." ); } return resolved; } function resolveExternalUrl(url) { ensureBrowserEnvironment(); const trimmedUrl = url.trim(); if (!trimmedUrl) { throw new Error("useSuspenseFavicon expected a non-empty external url."); } const resolved = new URL(trimmedUrl, document.baseURI); if (resolved.origin === window.location.origin) { throw new Error( "useSuspenseFavicon expected external updates to resolve to a different origin." ); } return resolved; } function updateFaviconURL(config) { ensureBrowserEnvironment(); ensureManagedMode(); const managedLink = getOrCreateManagedLink(); if (config.kind === "same-origin") { const trimmedRelativeHref = config.relativeHref.trim(); const resolved2 = resolveSameOriginRelativeHref(trimmedRelativeHref); managedLink.setAttribute("href", trimmedRelativeHref); notifySubscribers(createSameOriginFavicon(resolved2, trimmedRelativeHref)); return; } const resolved = resolveExternalUrl(config.url); managedLink.setAttribute("href", resolved.href); notifySubscribers({ kind: "external", url: resolved.href, href: resolved.href }); } function useSuspenseFavicon(options = {}) { const { unmountStrategy = "restore-originals" } = options; const instanceId = useRef(/* @__PURE__ */ Symbol("useSuspenseFavicon")); const latestUnmountStrategy = useRef(unmountStrategy); latestUnmountStrategy.current = unmountStrategy; initializeResourceIfNeeded(); if (resource.status === "pending") { throw resource.promise; } if (resource.status === "rejected") { throw resource.error; } const [favicon, setFavicon] = useState2(resource.currentValue); useEffect2(() => { const subscriber = (nextFavicon) => { setFavicon(nextFavicon); }; resource.subscribers.add(subscriber); resource.instanceCount += 1; resource.instanceStrategies.set( instanceId.current, latestUnmountStrategy.current ); setFavicon(resource.currentValue); return () => { resource.subscribers.delete(subscriber); resource.instanceStrategies.delete(instanceId.current); resource.instanceCount = Math.max(0, resource.instanceCount - 1); if (resource.instanceCount === 0) { cleanupAfterLastUnmount(latestUnmountStrategy.current); } }; }, []); useEffect2(() => { resource.instanceStrategies.set(instanceId.current, unmountStrategy); }, [unmountStrategy]); const controls = useMemo( () => ({ updateFaviconURL }), [] ); return [favicon, controls]; } // src/hooks/useSuspenseLocalStorageState.ts import { useState as useState3, useEffect as useEffect3, useCallback, useMemo as useMemo2 } 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 createInitializationPromise2(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 = createInitializationPromise2(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] = useState3(initialValue); const [isDeleted, setIsDeleted] = useState3(false); const customEventTypeName = useMemo2(() => { return `rooks-${key}-localstorage-update`; }, [key]); useEffect3(() => { 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] ); 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 = useCallback( (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: () => { 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 useState4, useEffect as useEffect4, useCallback as useCallback2, useMemo as useMemo3 } 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 createInitializationPromise3(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 = createInitializationPromise3(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] = useState4(initialValue); const [isDeleted, setIsDeleted] = useState4(false); const customEventTypeName = useMemo3(() => { return `rooks-${key}-sessionstorage-update`; }, [key]); useEffect4(() => { 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] ); useEffect4(() => { if (typeof window !== "undefined") { window.addEventListener("storage", handleStorageEvent); return () => { window.removeEventListener("storage", handleStorageEvent); }; } }, [handleStorageEvent]); useEffect4(() => { 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 = useMemo3(() => ({ 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 useState5, useCallback as useCallback3, useMemo as useMemo4, useRef as useRef2 } 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 createInitializationPromise4(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 = createInitializationPromise4(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] = useState5(initialValue); const channelName = useMemo4(() => { return `rooks-indexeddb-${dbName}-${storeName}`; }, [dbName, storeName]); const initializerRef = useRef2(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 = useMemo4(() => ({ 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 { useSuspenseFavicon, useSuspenseIndexedDBState, useSuspenseLocalStorageState, useSuspenseNavigatorBattery, useSuspenseNavigatorUserAgentData, useSuspenseSessionStorageState };