export function readUrlState(defaults = {}) { const params = new URLSearchParams(window.location.search); return Object.fromEntries(Object.entries(defaults).map(([key, fallback]) => [ key, params.has(key) ? params.get(key) : fallback ])); } export function writeUrlState(changes, { replace = true } = {}) { const url = new URL(window.location.href); Object.entries(changes).forEach(([key, value]) => { if (value === undefined || value === null || value === '' || value === false) { url.searchParams.delete(key); } else if (Array.isArray(value)) { url.searchParams.delete(key); value.forEach(item => url.searchParams.append(key, String(item))); } else { url.searchParams.set(key, String(value)); } }); window.history[replace ? 'replaceState' : 'pushState']({}, '', `${url.pathname}${url.search}${url.hash}`); } export function readUrlValues(key) { return new URLSearchParams(window.location.search).getAll(key); } export function debounce(callback, delay = 320) { let timer = null; return (...args) => { window.clearTimeout(timer); timer = window.setTimeout(() => callback(...args), delay); }; }