UNPKG

vuetify

Version:

Vue Material Component Framework

148 lines (147 loc) 5.55 kB
/* eslint-disable max-statements */ /* eslint-disable no-labels */ // Utilities import { computed, shallowRef, unref, watchEffect, createVNode as _createVNode, Fragment as _Fragment } from 'vue'; import { getPropertyFromItem, propsFactory, wrapInArray } from "../util/index.js"; // Types /** * - boolean: match without highlight * - number: single match (index), length already known * - []: single match (start, end) * - [][]: multiple matches (start, end), shouldn't overlap */ // Composables export const defaultFilter = (value, query, item) => { if (value == null || query == null) return -1; if (!query.length) return 0; value = value.toString().toLocaleLowerCase(); query = query.toString().toLocaleLowerCase(); const result = []; let idx = value.indexOf(query); while (~idx) { result.push([idx, idx + query.length]); idx = value.indexOf(query, idx + query.length); } return result.length ? result : -1; }; function normaliseMatch(match, query) { if (match == null || typeof match === 'boolean' || match === -1) return; if (typeof match === 'number') return [[match, match + query.length]]; if (Array.isArray(match[0])) return match; return [match]; } export const makeFilterProps = propsFactory({ customFilter: Function, customKeyFilter: Object, filterKeys: [Array, String], filterMode: { type: String, default: 'intersection' }, noFilter: Boolean }, 'filter'); export function filterItems(items, query, options) { const array = []; // always ensure we fall back to a functioning filter const filter = options?.default ?? defaultFilter; const keys = options?.filterKeys ? wrapInArray(options.filterKeys) : false; const customFiltersLength = Object.keys(options?.customKeyFilter ?? {}).length; if (!items?.length) return array; loop: for (let i = 0; i < items.length; i++) { const [item, transformed = item] = wrapInArray(items[i]); const customMatches = {}; const defaultMatches = {}; let match = -1; if ((query || customFiltersLength > 0) && !options?.noFilter) { if (typeof item === 'object') { const filterKeys = keys || Object.keys(transformed); for (const key of filterKeys) { const value = getPropertyFromItem(transformed, key); const keyFilter = options?.customKeyFilter?.[key]; match = keyFilter ? keyFilter(value, query, item) : filter(value, query, item); if (match !== -1 && match !== false) { if (keyFilter) customMatches[key] = normaliseMatch(match, query);else defaultMatches[key] = normaliseMatch(match, query); } else if (options?.filterMode === 'every') { continue loop; } } } else { match = filter(item, query, item); if (match !== -1 && match !== false) { defaultMatches.title = normaliseMatch(match, query); } } const defaultMatchesLength = Object.keys(defaultMatches).length; const customMatchesLength = Object.keys(customMatches).length; if (!defaultMatchesLength && !customMatchesLength) continue; if (options?.filterMode === 'union' && customMatchesLength !== customFiltersLength && !defaultMatchesLength) continue; if (options?.filterMode === 'intersection' && (customMatchesLength !== customFiltersLength || !defaultMatchesLength)) continue; } array.push({ index: i, matches: { ...defaultMatches, ...customMatches } }); } return array; } export function useFilter(props, items, query, options) { const filteredItems = shallowRef([]); const filteredMatches = shallowRef(new Map()); const transformedItems = computed(() => options?.transform ? unref(items).map(item => [item, options.transform(item)]) : unref(items)); watchEffect(() => { const _query = typeof query === 'function' ? query() : unref(query); const strQuery = typeof _query !== 'string' && typeof _query !== 'number' ? '' : String(_query); const results = filterItems(transformedItems.value, strQuery, { customKeyFilter: { ...props.customKeyFilter, ...unref(options?.customKeyFilter) }, default: props.customFilter, filterKeys: props.filterKeys, filterMode: props.filterMode, noFilter: props.noFilter }); const originalItems = unref(items); const _filteredItems = []; const _filteredMatches = new Map(); results.forEach(_ref => { let { index, matches } = _ref; const item = originalItems[index]; _filteredItems.push(item); _filteredMatches.set(item.value, matches); }); filteredItems.value = _filteredItems; filteredMatches.value = _filteredMatches; }); function getMatches(item) { return filteredMatches.value.get(item.value); } return { filteredItems, filteredMatches, getMatches }; } export function highlightResult(name, text, matches) { if (matches == null || !matches.length) return text; return matches.map((match, i) => { const start = i === 0 ? 0 : matches[i - 1][1]; const result = [_createVNode("span", { "class": `${name}__unmask` }, [text.slice(start, match[0])]), _createVNode("span", { "class": `${name}__mask` }, [text.slice(match[0], match[1])])]; if (i === matches.length - 1) { result.push(_createVNode("span", { "class": `${name}__unmask` }, [text.slice(match[1])])); } return _createVNode(_Fragment, null, [result]); }); } //# sourceMappingURL=filter.js.map