vuetify
Version:
Vue Material Component Framework
148 lines (147 loc) • 5.55 kB
JavaScript
/* 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