UNPKG

uikit

Version:

UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.

208 lines (170 loc) • 5.32 kB
import { $, $$, append, attr, css, data, fastdom, children as getChildren, hasClass, includes, isEmpty, isEqual, isTag, isUndefined, matches, toggleClass, trigger, } from 'uikit-util'; import { parseOptions } from '../api/options'; import Animate from '../mixin/animate'; import { keyMap } from '../util/keys'; export default { mixins: [Animate], args: 'target', props: { target: String, selActive: Boolean, }, data: { target: '', selActive: false, attrItem: 'uk-filter-control', cls: 'uk-active', duration: 250, }, computed: { children: ({ target }, $el) => $$(`${target} > *`, $el), toggles: ({ attrItem }, $el) => $$(`[${attrItem}],[data-${attrItem}]`, $el), }, watch: { toggles(toggles) { this.updateState(); const actives = $$(this.selActive, this.$el); for (const toggle of toggles) { if (this.selActive !== false) { toggleClass(toggle, this.cls, includes(actives, toggle)); } const button = findButton(toggle); if (isTag(button, 'a')) { attr(button, 'role', 'button'); } } }, children(list, prev) { if (prev) { this.updateState(); } }, }, events: { name: 'click keydown', delegate: ({ attrItem }) => `[${attrItem}],[data-${attrItem}]`, handler(e) { if (e.type === 'keydown' && e.keyCode !== keyMap.SPACE) { return; } if (e.target.closest('a,button')) { e.preventDefault(); this.apply(e.current); } }, }, methods: { apply(el) { const prevState = this.getState(); const newState = mergeState(el, this.attrItem, this.getState()); if (!isEqualState(prevState, newState)) { this.setState(newState); } }, getState() { return this.toggles .filter((item) => hasClass(item, this.cls)) .reduce((state, el) => mergeState(el, this.attrItem, state), { filter: { '': '' }, sort: [], }); }, async setState(state, animate = true) { state = { filter: { '': '' }, sort: [], ...state }; trigger(this.$el, 'beforeFilter', [this, state]); for (const toggle of this.toggles) { toggleClass(toggle, this.cls, matchFilter(toggle, this.attrItem, state)); } await Promise.all( $$(this.target, this.$el).map((target) => { const filterFn = () => applyState(state, target, getChildren(target)); return animate ? this.animate(filterFn, target) : filterFn(); }), ); trigger(this.$el, 'afterFilter', [this]); }, updateState() { fastdom.write(() => this.setState(this.getState(), false)); }, }, }; function getFilter(el, attr) { return parseOptions(data(el, attr), ['filter']); } function isEqualState(stateA, stateB) { return ['filter', 'sort'].every((prop) => isEqual(stateA[prop], stateB[prop])); } function applyState(state, target, children) { const selector = Object.values(state.filter).join(''); for (const el of children) { css(el, 'display', selector && !matches(el, selector) ? 'none' : ''); } const [sort, order] = state.sort; if (sort) { const sorted = sortItems(children, sort, order); if (!isEqual(sorted, children)) { append(target, sorted); } } } function mergeState(el, attr, state) { const { filter, group, sort, order = 'asc' } = getFilter(el, attr); if (filter || isUndefined(sort)) { if (group) { if (filter) { delete state.filter['']; state.filter[group] = filter; } else { delete state.filter[group]; if (isEmpty(state.filter) || '' in state.filter) { state.filter = { '': filter || '' }; } } } else { state.filter = { '': filter || '' }; } } if (!isUndefined(sort)) { state.sort = [sort, order]; } return state; } function matchFilter( el, attr, { filter: stateFilter = { '': '' }, sort: [stateSort, stateOrder] }, ) { const { filter = '', group = '', sort, order = 'asc' } = getFilter(el, attr); return isUndefined(sort) ? (group in stateFilter && filter === stateFilter[group]) || (!filter && group && !(group in stateFilter) && !stateFilter['']) : stateSort === sort && stateOrder === order; } function sortItems(nodes, sort, order) { return [...nodes].sort( (a, b) => data(a, sort).localeCompare(data(b, sort), undefined, { numeric: true }) * (order === 'asc' || -1), ); } function findButton(el) { return $('a,button', el) || el; }