UNPKG

epic-state

Version:

Reactive state management for frontend libraries.

81 lines (65 loc) 2.9 kB
import { log } from '../../helper' import type { ConfigurablePlugin, Plugin, PluginActions, ProxyObject, Value } from '../../types' // Nested object values are not persisted. const isTopLevelValue = (value: Value) => typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' function replaceUrl(queryParams: URLSearchParams) { const newUrl = `${window.location.origin}${window.location.pathname}?${queryParams.toString()}` window.history.replaceState({ page: 2 }, '', newUrl) // TODO replaceState will not work in tests, but is assigning href still necessary in browser? window.location.href = newUrl } function updateUrlParameter(property: string, value: Value) { const queryParams = new URLSearchParams(window.location.search) queryParams.set(property, value) replaceUrl(queryParams) } function initializeUrl(state: ProxyObject, properties: string[]) { const queryParams = new URLSearchParams(window.location.search) // Override state with initial URL parameters. for (const [key, value] of queryParams.entries()) { if (Object.hasOwn(state, key) && key !== 'plugin' && (properties.length === 0 || properties.includes(key))) { // @ts-ignore state[key] = typeof state[key] === 'number' ? Number(value) : value } } // Override URL parameters with initial state. for (const [key, value] of Object.entries(state)) { if (Object.hasOwn(state, key) && isTopLevelValue(value) && key !== 'plugin' && (properties.length === 0 || properties.includes(key))) { queryParams.set(key, String(value)) } } replaceUrl(queryParams) } type Configuration = string[] // Persist state to URL. export const persistUrl: ConfigurablePlugin<Configuration> = (...configuration: Configuration | ['initialize', ProxyObject?]) => { let properties: Configuration = [] const actions = { set: ({ property, value, previousValue }) => { if (typeof property === 'symbol') { log(`Symbol properties ${String(property)} cannot be added to the URL in persistUrl plugin`, 'warning') return } if (value === previousValue || (properties.length > 0 && !properties.includes(property))) { return } updateUrlParameter(property, value) }, } as PluginActions if (configuration[0] === 'initialize') { if (!configuration[1]) { log('persistUrl plugin cannot be registered globally', 'warning') } initializeUrl(configuration[1] as ProxyObject, properties) return actions } properties = properties.concat((configuration as Configuration) ?? []) const configuredPlugin: Plugin = (...innerConfiguration: any) => { if (innerConfiguration[0] !== 'initialize') { log('persistUrl: Plugin has already been configured', 'warning') } initializeUrl(innerConfiguration[1] as any, properties) return actions } return configuredPlugin }