UNPKG

react-use-url-state

Version:
274 lines (268 loc) 7.69 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); // packages/core/src/index.ts var index_exports = {}; __export(index_exports, { useUrlState: () => useUrlState }); module.exports = __toCommonJS(index_exports); var import_react3 = require("react"); // packages/core/src/utils.ts var import_react = require("react"); function isNil(value) { return value === null || value === void 0; } function urlParamsToObject(params) { const object = {}; for (const [key, value] of params.entries()) { if (isNil(object[key])) { object[key] = value; continue; } const currentValue = object[key]; if (Array.isArray(currentValue)) { currentValue.push(value); } else { object[key] = [currentValue, value]; } } return object; } function shallowEqual(a, b) { if (a === b) { return true; } if (typeof a !== "object" || typeof b !== "object") { return false; } if (a === null || b === null) { return false; } const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) { return false; } for (const key of aKeys) { if ("toString" in a && "toString" in b && typeof a.toString === "function" && typeof b.toString === "function") { if (a.toString() !== b.toString()) { return false; } } else if (a[key] !== b[key]) { return false; } } return true; } function useStableSchema(schema) { const ref = (0, import_react.useRef)(schema); if (ref.current === schema) { return ref.current; } if (!shallowEqual(ref.current.shape, schema.shape)) { ref.current = schema; } return ref.current; } function serializeValue(value) { if (isNil(value)) { return void 0; } if (typeof value === "object") { if (value instanceof Date) { return value.toISOString(); } else if (Array.isArray(value)) { return value.map((v) => { if (typeof v === "object") { return JSON.stringify(v); } return serializeValue(v); }); } else { return value?.toString?.(); } } return value?.toString(); } function serializeObjectToUrlParams(object) { const params = new URLSearchParams(); Object.keys(object).forEach((key) => { const value = serializeValue(object[key]); if (value === void 0 || value === null) { return; } if (Array.isArray(value)) { value.forEach((v) => { if (isNil(v)) { return; } params.append(key, v); }); return; } params.append(key, value); }); return `?${params.toString()}`; } // packages/core/src/controller.ts var UrlStateController = class _UrlStateController { constructor(options) { this.options = options; this.stateString = ""; this.previousHref = ""; this.interval = 0; this.subscribers = /* @__PURE__ */ new Map(); this.options = { poolingIntervalMs: 100, ...options }; } static { this.singleton = null; } static getUrlStateController() { if (!_UrlStateController.singleton) { _UrlStateController.singleton = new _UrlStateController({}); } return _UrlStateController.singleton; } push(href) { window.history.pushState({}, "", href); setTimeout(() => { this.onSearchParamsChange(); }, 0); } subscribe(fn) { this.subscribers.set(fn, fn); const newSearchParams = new URLSearchParams(this.stateString); const search = urlParamsToObject(newSearchParams); setTimeout(() => fn(search), 0); if (!this.interval) { this.startPolling(); } } unsubscribe(fn) { this.subscribers.delete(fn); if (this.subscribers.size === 0) { this.stopPolling(); } } onSearchParamsChange() { if (window.location.href !== this.previousHref) { this.previousHref = window.location.href; const newSearchParams = new URLSearchParams(window.location.search); const search = urlParamsToObject(newSearchParams); this.subscribers.forEach((subscriber) => subscriber(search)); } } startPolling() { if (typeof window !== "undefined") { this.interval = setInterval(() => { this.onSearchParamsChange(); }, this.options.poolingIntervalMs); } } stopPolling() { if (this.interval) { clearInterval(this.interval); this.interval = 0; } } }; // packages/core/src/handlers.ts var import_react2 = require("react"); function useHandlers(controller, stateRef) { const setState = (0, import_react2.useCallback)( (state) => { if (typeof state === "function") { state = state(stateRef.current.data); } const href = serializeObjectToUrlParams(state); controller.push(href); }, [controller, stateRef] ); const setValues = (0, import_react2.useCallback)( (state) => { if (typeof state === "function") { state = state(stateRef.current.data); } const href = serializeObjectToUrlParams({ ...stateRef.current.data, ...state }); controller.push(href); }, [controller, stateRef] ); const setValue = (0, import_react2.useCallback)( (key, value) => { const href = serializeObjectToUrlParams({ ...stateRef.current.data, [key]: value }); controller.push(href); }, [controller, stateRef] ); return (0, import_react2.useMemo)( () => ({ setState, setValue, setValues }), [setState, setValue, setValues] ); } // packages/core/src/index.ts function useUrlState(schema, options) { schema = useStableSchema(schema); const controller = UrlStateController.getUrlStateController(); const [state, setState] = (0, import_react3.useState)({ data: null, error: null, isError: false, isReady: false }); const stateRef = (0, import_react3.useRef)(state); stateRef.current = state; const recalculateState = (0, import_react3.useCallback)( (params) => { const validationResult = schema.safeParse(params); const result = validationResult.success ? { success: true, data: validationResult.data, error: null } : { success: false, data: null, error: validationResult.error }; setState({ data: result.data, isError: !result.success, error: result.error, isReady: true }); }, [schema] ); (0, import_react3.useEffect)(() => { controller.subscribe(recalculateState); return () => { controller.unsubscribe(recalculateState); }; }, [controller, recalculateState]); const handlers = useHandlers(controller, stateRef); (0, import_react3.useEffect)(() => { if (state.isReady && options?.applyInitialValue) { handlers.setValues(state.data || {}); } }, [handlers, options?.applyInitialValue, state.isReady]); return { ...state, ...handlers }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { useUrlState });