import * as React from 'react'; /** * Exxat DS — persisted-state primitive. * * Two layers in one file: * * 1. **Low-level storage** — `getStorageItem`, `setStorageItem`, * `scheduleStorageWrite`, `subscribeToStorageKey`, * `clearAllPersistedState`. Use when you need imperative control * (writing on `beforeunload`, listing keys for a Settings panel, * draining a queue). * * 2. **React hook** — `usePersistedState(key, default, opts?)`. The * drop-in replacement for `useState` that survives reloads. 90% of * consumers should reach for this and not the low-level helpers. * * Centralised so future migrations (server-side sync, IndexedDB * fallback, encryption) only have to touch one file. **Don't read or * write `localStorage` directly inside DS code** — go through here. */ /** Every DS-written storage key starts with this prefix. */ declare const EXXAT_STORAGE_PREFIX = "exxat-ds:"; /** Debounce window for collapsed writes. */ declare const DEFAULT_DEBOUNCE_MS = 400; type StorageDriver = "local" | "session"; /** * Compose a fully-qualified storage key. Pass the unprefixed key (e.g. * `"sidebar:collapsed"`) — the prefix is added here. If the caller already * passes a prefixed key (e.g. legacy `exxat-ds:placements:lifecycle:v1:main`) * we don't double-prefix. */ declare function namespacedKey(key: string): string; declare function getStorageItem(key: string, driver?: StorageDriver): string | null; declare function setStorageItem(key: string, value: string, driver?: StorageDriver): void; declare function removeStorageItem(key: string, driver?: StorageDriver): void; /** * Schedule a debounced write. Successive calls for the same `key` reset the * timer so only the trailing value lands in storage. Use this for the hot * path (column resize, search keystrokes, scroll position) — anything that * fires faster than ~10Hz. * * For one-shot writes (theme change, sidebar toggle) call `setStorageItem` * directly; the debounce is overhead you don't need. */ declare function scheduleStorageWrite(key: string, value: string, options?: { debounceMs?: number; driver?: StorageDriver; }): void; /** * Force any pending debounced writes to flush synchronously. Safe to call * on `beforeunload` so a tab close doesn't drop the last keystroke. */ declare function flushPendingStorageWrites(): void; type StorageListener = (newValue: string | null) => void; /** * Subscribe to changes for a single DS-namespaced key from *other tabs*. * The browser fires `storage` events on every tab except the one that * triggered the write, so this is the natural cross-tab sync mechanism. * * Returns an unsubscribe function. SSR-safe (returns a no-op on the server). */ declare function subscribeToStorageKey(key: string, listener: StorageListener): () => void; /** * Wipe every key the DS owns (anything starting with `exxat-ds:`). Exposed * for Settings → "Reset preferences" flows. Customer apps SHOULD wire this * to a settings button rather than letting users hunt through DevTools. */ declare function clearAllPersistedState(driver?: StorageDriver): void; /** * List every DS-namespaced key currently in storage. Useful for Settings * dashboards that want to show users what they can clear. */ declare function listPersistedKeys(driver?: StorageDriver): string[]; interface UsePersistedStateOptions { /** Storage driver. Default `"local"`. Use `"session"` for tab-scoped state. */ driver?: StorageDriver; /** * Schema version. Bump when the shape of `T` changes. Old payloads with a * different version are passed to `migrate`; if `migrate` is missing or * returns `undefined`, the persisted record is dropped. */ version?: number; /** * Migrate a stored payload from a previous version into the current * shape. Returning `undefined` discards the record (state falls back to * `defaultValue`). */ migrate?: (raw: unknown, fromVersion: number) => T | undefined; /** Custom serializer. Default is `JSON.stringify` (with envelope). */ serialize?: (value: T) => string; /** * Custom deserializer. Default is `JSON.parse`. Return `undefined` to * discard the payload (e.g. shape no longer matches and you don't have a * migration). */ deserialize?: (raw: string) => T | undefined; /** * Debounce window for writes. Default 400ms. Pass `0` for synchronous * writes (one-shot toggles). */ debounceMs?: number; /** * Listen for `storage` events from other tabs and reflect them locally. * Default `true`. */ syncAcrossTabs?: boolean; } /** * Persisted state hook. Drop-in replacement for `useState`. * * ```tsx * const [collapsed, setCollapsed] = usePersistedState("sidebar:collapsed", false) * const [theme, setTheme, clearTheme] = usePersistedState("theme", "light", { debounceMs: 0 }) * ``` * * Returns `[value, setValue, clear]` — `clear()` removes the entry from * storage and resets in-memory state to the latest `defaultValue`. * * **Disabling persistence at runtime:** pass `key = ""` (empty string) and * the hook degrades to plain `useState` — no reads, no writes, no `storage` * subscription, no namespacing. This lets components conditionally enable * persistence (e.g. via a `persistKey?: string` prop) without breaking the * rules of hooks. */ declare function usePersistedState(key: string, defaultValue: T, options?: UsePersistedStateOptions): [T, React.Dispatch>, () => void]; export { DEFAULT_DEBOUNCE_MS, EXXAT_STORAGE_PREFIX, type StorageDriver, type UsePersistedStateOptions, clearAllPersistedState, flushPendingStorageWrites, getStorageItem, listPersistedKeys, namespacedKey, removeStorageItem, scheduleStorageWrite, setStorageItem, subscribeToStorageKey, usePersistedState };