UNPKG

svelte-query-params

Version:

A lightweight, dead-simple, type-safe reactive query parameter store built for Svelte 5.

285 lines (278 loc) 8.93 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/lib/index.ts var lib_exports = {}; __export(lib_exports, { createUseQueryParams: () => createUseQueryParams }); module.exports = __toCommonJS(lib_exports); // src/lib/create-params.svelte.ts var import_svelte = require("svelte"); // src/lib/adapters/browser.ts function browser(options = {}) { const { windowObj = window, replace = false } = options; const replaceState = windowObj.history.replaceState.bind(windowObj.history); const pushState = windowObj.history.pushState.bind(windowObj.history); const update = replace ? replaceState : pushState; return { isBrowser: () => typeof window !== "undefined", browser: { read: () => windowObj.location, save: (search, hash) => update(null, "", `${search.length ? search : "?"}${hash}`) }, server: { save: () => { } } }; } // src/lib/search-params.ts var import_reactivity = require("svelte/reactivity"); var ReactiveSearchParams = class extends import_reactivity.SvelteURLSearchParams { get raw() { const raw = {}; for (const [key, value] of this.entries()) { if (key in raw) { const existing = raw[key]; if (Array.isArray(existing)) { existing.push(value); } else { raw[key] = [existing, value]; } } else { raw[key] = value; } } return raw; } get uniqueKeys() { return [...new Set(this.keys())]; } get search() { return this.size ? `?${this.toString()}` : ""; } clear() { for (const key of this.uniqueKeys) { this.delete(key); } } /** * Replaces all query params under the given key with a new array of values */ set(key, values) { if (Array.isArray(values)) { this.delete(key); for (const arrayValue of values) { this.append(key, arrayValue); } } else { super.set(key, values); } } setFromObject(query) { for (const [key, value] of Object.entries(query)) { this.set(key, value); } } setFromSearch(query) { this.clear(); const params = new URLSearchParams(query); for (const [key, value] of params.entries()) { this.append(key, value); } } changed(key, value) { const compare = Array.isArray(value) ? value : [value]; const existing = this.getAll(key).sort(); if (compare.length !== existing.length) return true; const sortedNew = [...compare].sort(); for (let i = 0; i < existing.length; i++) { if (existing[i] !== sortedNew[i]) return true; } return false; } equals(params) { const paramKeys = Object.keys(params); if (this.uniqueKeys.length !== paramKeys.length) return false; for (const key of this.uniqueKeys) { if (this.changed(key, params[key])) return false; } return true; } }; // src/lib/utils.ts var import_valibot = require("valibot"); function parseObject(schemas, input) { return Object.fromEntries( Object.entries(schemas).map(([key, schema]) => [ key, parseValue(key, schema, input[key]) ]) ); } function parseValue(key, schema, value) { if (typeof schema === "function") return schema(value); if (isZodSchema(schema)) return schema.parse(value); if (isValibotSchema(schema)) return (0, import_valibot.parse)(schema, value); const name = schema.constructor.name; throw new Error( `Unknown validator type (${name}) for param "${key}" (value: ${value})` ); } function isZodSchema(obj) { return typeof obj === "object" && obj && "parse" in obj && "safeParse" in obj && typeof obj.parse === "function" && typeof obj.safeParse === "function"; } function isValibotSchema(obj) { return typeof obj === "object" && obj && "async" in obj && "kind" in obj && "_run" in obj && obj.kind === "schema" && typeof obj.async === "boolean" && typeof obj._run === "function"; } function parseQueryParams(params, schemas) { if (typeof schemas === "function") return schemas(params); if (isZodSchema(schemas)) return schemas.parse(params); if (isValibotSchema(schemas)) return (0, import_valibot.parse)(schemas, params); return parseObject(schemas, params); } function mapValues(object, mapFn) { return Object.fromEntries( Object.entries(object).map(([key, value]) => [key, mapFn(value)]) ); } function debounce(func, delay) { if (delay === 0) return func; let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func(...args); }, delay); }; } // src/lib/create-params.svelte.ts function createUseQueryParams(validators, options = {}) { const { debounce: delay = 0, windowObj = typeof window === "undefined" ? void 0 : window, adapter = browser({ windowObj }), serialise = (value) => typeof value === "string" ? value : JSON.stringify(value) } = options; const searchParams = new ReactiveSearchParams(); const parsedQuery = $derived(parseQueryParams(searchParams.raw, validators)); function readFromBrowser() { searchParams.setFromSearch(adapter.browser.read().search); } const persistToBrowser = debounce((search, hash) => { return (0, import_svelte.tick)().then(() => adapter.browser.save(search, hash)); }, delay); function persistParams() { adapter.isBrowser() ? persistToBrowser(searchParams.search, adapter.browser.read().hash) : adapter.server.save(searchParams.search); } function serialiseValue(value) { return Array.isArray(value) ? value.map(serialise) : serialise(value); } let unsubscribe; if (windowObj) { unsubscribe = addWindowListener(windowObj, readFromBrowser); } return function useQueryParams(url) { searchParams.setFromSearch(url.search); const params = {}; for (const key of Object.keys(parsedQuery)) { Object.defineProperty(params, key, { enumerable: true, configurable: true, get() { return parsedQuery[key]; }, set(newValue) { const value = serialiseValue(newValue); if (searchParams.changed(key, value)) { searchParams.setFromObject({ [key]: value }); persistParams(); } } }); } if (typeof window !== "undefined" && !unsubscribe) { unsubscribe = addWindowListener(window, readFromBrowser); } return [ params, { get raw() { return searchParams.raw; }, get search() { return searchParams.search; }, get all() { return { ...searchParams.raw, ...parsedQuery }; }, keys() { return Object.keys(parsedQuery); }, entries() { return Object.entries(parsedQuery); }, set(params2) { const updated = mapValues(params2, serialiseValue); if (!searchParams.equals(updated)) { searchParams.clear(); searchParams.setFromObject(updated); persistParams(); } }, update(params2) { const updated = mapValues(params2, serialiseValue); if (!searchParams.equals(updated)) { searchParams.setFromObject(updated); persistParams(); } }, remove(...params2) { params2.map((param) => searchParams.delete(param)); persistParams(); }, unsubscribe() { return unsubscribe?.(); } } ]; }; } function addWindowListener(windowObj, update) { const replaceState = windowObj.history.replaceState.bind(windowObj.history); const pushState = windowObj.history.pushState.bind(windowObj.history); windowObj.history.replaceState = (...args) => { replaceState(...args); update(); }; windowObj.history.pushState = (...args) => { pushState(...args); update(); }; windowObj.addEventListener("popstate", update); return () => { windowObj.removeEventListener("popstate", update); windowObj.history.pushState = pushState; windowObj.history.replaceState = replaceState; }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createUseQueryParams });