UNPKG

@smkit/ui

Version:

UI Kit of SberMarketing

118 lines (117 loc) 4.17 kB
import { derived, get, writable } from 'svelte/store'; import { createEventDispatcher, getContext, setContext, tick } from 'svelte'; import { nanoid } from 'nanoid'; export var ViewType; (function (ViewType) { ViewType["Modal"] = "modal"; ViewType["Embedded"] = "embedded"; })(ViewType || (ViewType = {})); function defaultSearchBy(option) { if (typeof option === 'object') { return Object.values(option) .map((v) => defaultSearchBy(v)) .join(' ') .toLowerCase(); } return option.toString(); } export class Combobox { props; options; selected; state; search; focus; filtered; ctxKey; multiple; disabled; dispatch; lastOptions = ''; searchIndex; groupBy; searchBy; constructor({ selected, groupBy, searchBy, multiple, config, disabled }) { this.groupBy = groupBy; this.searchBy = searchBy ?? defaultSearchBy; this.props = { selected, groupBy, searchBy, multiple, disabled, config }; this.ctxKey = config?.ctxKey ?? nanoid(10); this.multiple = multiple; this.disabled = writable(disabled); this.dispatch = createEventDispatcher(); this.options = writable(); this.selected = writable(multiple ? (selected ?? []) : selected ? [selected] : []); this.search = writable(''); this.focus = writable(false); this.state = writable(); this.searchIndex = {}; this.state.subscribe((state) => { if (!state) return; const selected = Object.entries(state) .filter(([index, checked]) => checked) .map(([index, checked]) => this.searchIndex[index]); this.selected.set(selected); tick().then(() => this.dispatch('select', this.multiple ? selected : selected[0])); }); this.filtered = derived([this.options, this.search], ([, $search]) => { if (!this.options) return []; return Object.entries(this.searchIndex) .filter(([index]) => index.toLowerCase().includes($search.toLowerCase())) .map(([, option]) => option); }); setContext(this.ctxKey, this); } render(options) { const memorizedOptions = JSON.stringify(options); if (memorizedOptions === this.lastOptions) return; this.lastOptions = memorizedOptions; const selected = get(this.selected); const selectedSet = new Set(selected.map((s) => this.searchBy(s))); this.searchIndex = Object.fromEntries(options.map((opt) => [this.searchBy(opt), opt])); this.state.set(Object.fromEntries(Object.keys(this.searchIndex).map((index) => [index, selectedSet.has(index)]))); this.options.set(options); } static get self() { const ctxKey = getContext('ctxKey'); if (!ctxKey) throw Error('Combobox: ctxKey must be set inside Combobox component'); return getContext(ctxKey); } toggle(option) { const index = this.searchBy(option); this.state.update((state) => { this.dispatch('toggle', { option, state: !state[index] }); if (!this.multiple) { for (const key of Object.keys(state)) { state[key] = false; } state[index] = true; this.focus.set(false); return state; } state[index] = !state[index]; return state; }); } toggleAll() { if (!this.multiple) throw Error('smkit.Combobox: ToggleAll is forbidden for single select version'); const isSelectedAll = this.isSelectedAll(); this.state.update((v) => { for (const k of Object.keys(v)) { v[k] = !isSelectedAll; } return v; }); } isSelectedAll() { const current = Object.values(get(this.state)); return current.filter((v) => v).length === current.length; } close() { this.focus.set(false); } }