@smkit/ui
Version:
UI Kit of SberMarketing
118 lines (117 loc) • 4.17 kB
JavaScript
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);
}
}