@veebisepad/vue-query-filters
Version:
Reactive utility for managing URL query parameters in Vue 3.
195 lines (192 loc) • 6.34 kB
JavaScript
// 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