UNPKG

@veebisepad/vue-query-filters

Version:

Reactive utility for managing URL query parameters in Vue 3.

195 lines (192 loc) 6.34 kB
// src/utils.ts var cloneDefault = (value) => { if (Array.isArray(value)) { return [...value]; } if (value && typeof value === "object") { return JSON.parse(JSON.stringify(value)); } return value; }; function getSearchParams(location) { const resolved = resolveLocation(location); if (!resolved) { return new URLSearchParams(); } return new URLSearchParams(resolved.search); } function resolveLocation(location) { if (location) { return location; } if (typeof window !== "undefined" && window.location) { return window.location; } return void 0; } // src/createFilterFactory.ts var createFilterFactory = (options) => { const keyTransformer = options?.keyTransformer || ((key) => key); const transformKey = (key) => keyTransformer(key); const allowedFilters = { /** * Creates a filter for a single value * @template SingleType Type of the filter value * @param defaultValue Default value when filter key is not present * @returns A filter object handling single values */ single: (defaultValue = null) => ({ defaultValue: cloneDefault(defaultValue), parseQueryParam(value) { return !value ? cloneDefault(this.defaultValue) : value; }, serializeQueryParam(value) { return !value ? null : String(value); }, hasValue(filterValue, value) { return filterValue?.toString() === value.toString(); }, transformKey }), /** * Creates a filter for multiple values * @template MultipleType Type of each item in the array * @param defaultValue Default array when filter key is not present * @returns A filter object handling arrays */ multiple: (defaultValue = []) => ({ defaultValue: cloneDefault(defaultValue), parseQueryParam(value, delimiter) { return !value ? cloneDefault(this.defaultValue) : value.split(delimiter); }, serializeQueryParam(value, delimiter) { return !Array.isArray(value) || !value.length ? null : value.join(delimiter); }, hasValue(filterValue, value) { return Array.isArray(filterValue) ? filterValue.includes(value) : false; }, transformKey }), /** * Creates a range filter * @template RangeType Type of the range endpoints * @param defaultValue Default range when filter key is not present * @returns A filter object handling { from: RangeType; to: RangeType } */ range: (defaultValue = { from: null, to: null }) => ({ defaultValue: cloneDefault(defaultValue), parseQueryParam(value, delimiter) { if (!value) return cloneDefault(this.defaultValue); const [from, to] = value.split(delimiter); return { from: from ? from : this.defaultValue.from, to: to ? to : this.defaultValue.to }; }, serializeQueryParam(value, delimiter) { if (!value || value.from === null && value.to === null) return null; return [value.from, value.to].join(delimiter); }, hasValue(filterValue, value) { return filterValue?.from?.toString() === value.toString() || filterValue?.to?.toString() === value.toString(); }, transformKey }), /** * Allows creating a custom filter * @template CustomType Type of the filter value * @param filter A fully-defined filter object * @returns The same filter, for custom scenarios */ custom: (filter) => { return { ...filter, transformKey }; } }; return allowedFilters; }; // src/useFilters.ts import { reactive } from "vue"; function useFilters(filters, initialOptions = {}) { let options = { delimiter: ",", preserveQueryOrder: true, ...initialOptions }; const queryParams = getSearchParams(options.location); const filterKeys = Object.keys(filters); const filterProps = Object.keys(filters).length > 0 ? Object.keys(filters).reduce( (carry, key) => { const filterConfig = filters[key]; const transformedKey = filterConfig.transformKey(key); const paramValue = queryParams.get(transformedKey); carry[key] = filterConfig.parseQueryParam(paramValue, options.delimiter); return carry; }, {} ) : {}; const hasCallback = !!options.onApply; const filterObject = { ...filterProps, toSearchParams() { return new URLSearchParams(this.toQueryObject()); }, get() { if (options.onApply) { options.onApply(options.preserveQueryOrder ? this.toOrderedQueryObject() : this.toQueryObject()); } else { console.warn("Cannot use get() - Filter callback is not set"); } }, toOrderedQueryObject(transformKeys = true) { const queryObject = this.toQueryObject(transformKeys); const orderedObject = {}; const currentParams = getSearchParams(options.location); currentParams.forEach((_, key) => { if (key in queryObject) { orderedObject[key] = queryObject[key]; } }); return Object.assign(orderedObject, queryObject); }, toQueryObject(transformKeys = true) { return filterKeys.reduce((queryObject, key) => { const paramValue = filters[key].serializeQueryParam(this[key], options.delimiter); if (paramValue) { const objectKey = transformKeys ? filters[key].transformKey(key) : key; queryObject[objectKey] = paramValue; } return queryObject; }, {}); }, data() { return filterProps; }, has(filter, value) { return filters[filter].hasValue(this[filter], value) ?? false; }, clear(filterKey, shouldGet = true) { const filtersToClear = Array.isArray(filterKey) ? filterKey : [filterKey]; if (filtersToClear.length === 0) return; filtersToClear.forEach((key) => { this[key] = cloneDefault(filters[key].defaultValue); }); if (hasCallback && shouldGet) { this.get(); } }, clearAll() { this.clear(filterKeys, true); }, setOptions(newOptions) { options = { ...options, ...newOptions }; } }; return reactive(filterObject); } // src/index.ts var factory = createFilterFactory(); export { createFilterFactory, factory, useFilters }; //# sourceMappingURL=index.js.map