UNPKG

nuqs

Version:

Type-safe search params state manager for React - Like useState, but stored in the URL query string

177 lines (173 loc) 4.95 kB
import { warn, debug, error } from './chunk-5WWTJYGR.js'; // src/utils.ts function safeParse(parser, value, key) { try { return parser(value); } catch (error2) { warn( "[nuqs] Error while parsing value `%s`: %O" + (key ? " (for key `%s`)" : ""), value, error2, key ); return null; } } function getDefaultThrottle() { if (typeof window === "undefined") return 50; const isSafari = Boolean(window.GestureEvent); if (!isSafari) { return 50; } try { const match = navigator.userAgent?.match(/version\/([\d\.]+) safari/i); return parseFloat(match[1]) >= 17 ? 120 : 320; } catch { return 320; } } // src/update-queue.ts var FLUSH_RATE_LIMIT_MS = getDefaultThrottle(); var updateQueue = /* @__PURE__ */ new Map(); var queueOptions = { history: "replace", scroll: false, shallow: true, throttleMs: FLUSH_RATE_LIMIT_MS }; var transitionsQueue = /* @__PURE__ */ new Set(); var lastFlushTimestamp = 0; var flushPromiseCache = null; function getQueuedValue(key) { return updateQueue.get(key); } function resetQueue() { updateQueue.clear(); transitionsQueue.clear(); queueOptions.history = "replace"; queueOptions.scroll = false; queueOptions.shallow = true; queueOptions.throttleMs = FLUSH_RATE_LIMIT_MS; } function enqueueQueryStringUpdate(key, value, serialize, options) { const serializedOrNull = value === null ? null : serialize(value); debug("[nuqs queue] Enqueueing %s=%s %O", key, serializedOrNull, options); updateQueue.set(key, serializedOrNull); if (options.history === "push") { queueOptions.history = "push"; } if (options.scroll) { queueOptions.scroll = true; } if (options.shallow === false) { queueOptions.shallow = false; } if (options.startTransition) { transitionsQueue.add(options.startTransition); } queueOptions.throttleMs = Math.max( options.throttleMs ?? FLUSH_RATE_LIMIT_MS, Number.isFinite(queueOptions.throttleMs) ? queueOptions.throttleMs : 0 ); return serializedOrNull; } function getSearchParamsSnapshotFromLocation() { return new URLSearchParams(location.search); } function scheduleFlushToURL({ getSearchParamsSnapshot = getSearchParamsSnapshotFromLocation, updateUrl, rateLimitFactor = 1 }) { if (flushPromiseCache === null) { flushPromiseCache = new Promise((resolve, reject) => { if (!Number.isFinite(queueOptions.throttleMs)) { debug("[nuqs queue] Skipping flush due to throttleMs=Infinity"); resolve(getSearchParamsSnapshot()); setTimeout(() => { flushPromiseCache = null; }, 0); return; } function flushNow() { lastFlushTimestamp = performance.now(); const [search, error2] = flushUpdateQueue({ updateUrl, getSearchParamsSnapshot }); if (error2 === null) { resolve(search); } else { reject(search); } flushPromiseCache = null; } function runOnNextTick() { const now = performance.now(); const timeSinceLastFlush = now - lastFlushTimestamp; const throttleMs = queueOptions.throttleMs; const flushInMs = rateLimitFactor * Math.max(0, Math.min(throttleMs, throttleMs - timeSinceLastFlush)); debug( "[nuqs queue] Scheduling flush in %f ms. Throttled at %f ms", flushInMs, throttleMs ); if (flushInMs === 0) { flushNow(); } else { setTimeout(flushNow, flushInMs); } } setTimeout(runOnNextTick, 0); }); } return flushPromiseCache; } function flushUpdateQueue({ updateUrl, getSearchParamsSnapshot }) { const search = getSearchParamsSnapshot(); if (updateQueue.size === 0) { return [search, null]; } const items = Array.from(updateQueue.entries()); const options = { ...queueOptions }; const transitions = Array.from(transitionsQueue); resetQueue(); debug("[nuqs queue] Flushing queue %O with options %O", items, options); for (const [key, value] of items) { if (value === null) { search.delete(key); } else { search.set(key, value); } } try { compose(transitions, () => { updateUrl(search, { history: options.history, scroll: options.scroll, shallow: options.shallow }); }); return [search, null]; } catch (err) { console.error(error(429), items.map(([key]) => key).join(), err); return [search, err]; } } function compose(fns, final) { const recursiveCompose = (index) => { if (index === fns.length) { return final(); } const fn = fns[index]; if (!fn) { throw new Error("Invalid transition function"); } fn(() => recursiveCompose(index + 1)); }; recursiveCompose(0); } export { FLUSH_RATE_LIMIT_MS, enqueueQueryStringUpdate, getQueuedValue, resetQueue, safeParse, scheduleFlushToURL };