UNPKG

jotai-location

Version:
137 lines (136 loc) • 5.53 kB
import { atom } from 'jotai/vanilla'; import { atomWithLocation, } from './atomWithLocation.js'; function warning(...data) { if (process.env.NODE_ENV !== 'production') { console.warn(...data); } } const applyLocation = (location, options) => { const url = new URL(window.location.href); if ('pathname' in location) { url.pathname = location.pathname; } if ('searchParams' in location) { const existingParams = new URLSearchParams(url.search); const newParams = location.searchParams; for (const [key, value] of newParams.entries()) { existingParams.set(key, value); } url.search = existingParams.toString(); } if ('hash' in location) { url.hash = location.hash; } if (options === null || options === void 0 ? void 0 : options.replace) { window.history.replaceState(window.history.state, '', url); } else { window.history.pushState(null, '', url); } }; /** * Creates an atom that manages a single search parameter. * * The atom automatically infers the type of the search parameter based on the * type of `defaultValue`. * * The atom's read function returns the current value of the search parameter. * The atom's write function updates the search parameter in the URL. * * @param key - The key of the search parameter. * @param defaultValue - The default value of the search parameter. * @returns A writable atom that manages the search parameter. */ export const atomWithSearchParams = (key, defaultValue, options) => { var _a; // Create an atom for managing location state, including search parameters. const locationAtom = atomWithLocation({ ...options, applyLocation: (_a = options === null || options === void 0 ? void 0 : options.applyLocation) !== null && _a !== void 0 ? _a : applyLocation, }); /** * Resolves the value of a search parameter based on the type of `defaultValue`. * * @param value - The raw value from the URL (could be `null` or `undefined`). * @returns The resolved value matching the type of `defaultValue`. */ const resolveValue = (value) => { // If the value is null, undefined, or not a string, return the default value. if (value === null || value === undefined) { return defaultValue; } // Determine the type of the default value and parse accordingly. if (typeof defaultValue === 'number') { if (value === '') { warning(`Empty string provided for key "${key}". Falling back to default value.`); return defaultValue; } const parsed = Number(value); if (!Number.isNaN(parsed)) { return parsed; } warning(`Expected a number for key "${key}", got "${value}".`); return defaultValue; } // If the default value is a boolean, check if the value is `true` or `false`. if (typeof defaultValue === 'boolean') { if (value === 'true') return true; if (value === 'false') return false; warning(`Expected a boolean for key "${key}", got "${value}".`); return defaultValue; } if (typeof defaultValue === 'string') { return value; } // Fallback to default value for unsupported types warning(`Unsupported defaultValue type for key "${key}".`); return defaultValue; }; /** * Converts the value into a string for use in the URL. * * Includes runtime type validation to ensure only compatible types are passed. * * @param value - The value to be serialized. * @returns The stringified value. */ const parseValue = (value) => { if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') { return String(value); } warning(`Unsupported value type for key "${key}":`, typeof value); throw new Error(`Unsupported value type for key "${key}".`); }; return atom( // Read function: Retrieves the current value of the search parameter. (get) => { const { searchParams } = get(locationAtom); // Resolve the value using the parsing logic. return resolveValue(searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(key)); }, // Write function: Updates the search parameter in the URL. (_, set, value) => { set(locationAtom, (prev) => { // Create a new instance of URLSearchParams to avoid mutating the original. const newSearchParams = new URLSearchParams(prev.searchParams); let nextValue; if (typeof value === 'function') { // If the new value is a function, compute it based on the current value. const currentValue = resolveValue(newSearchParams.get(key)); nextValue = value(currentValue); } else { // Otherwise, use the provided value directly. nextValue = value; } // Update the search parameter with the computed value. newSearchParams.set(key, parseValue(nextValue)); // Return the updated location state with new search parameters. return { ...prev, searchParams: newSearchParams }; }); }); };