import { ArrayField, WixPatternsContainer, KeyedItem, SelectState, ToggleState, } from '@wix/bex-core'; import { action, computed, makeObservable, reaction } from 'mobx'; import { EventEmitter } from '@wix/bex-core/events'; import { FilterToolbarState } from '../providers'; export interface DropdownSelectStateParams { container: WixPatternsContainer; toolbar: FilterToolbarState; select?: SelectState>; filter: ArrayField; maxSelected?: number; } export class DropdownSelectState { readonly container; readonly toolbar; readonly select: SelectState>; readonly _filter: DropdownSelectStateParams['filter']; readonly maxSelected: DropdownSelectStateParams['maxSelected']; readonly dropdown = new ToggleState(); readonly events = new EventEmitter(); constructor(params: DropdownSelectStateParams) { this.container = params.container; this.toolbar = params.toolbar; this.select = params.select ?? new SelectState>({ keyGetter: (value) => value.key, }); this._filter = params.filter; this.maxSelected = params.maxSelected; makeObservable(this, { filterKeyedItems: computed, filterKeyedItemsMap: computed, finalFilterKeyedItems: computed, finalFilterKeyedItemsMap: computed, reachedMax: computed, selectedItems: computed({ keepAlive: true, }), set: action, delete: action, deleteKey: action, init: action.bound, open: action.bound, clear: action.bound, syncSelectedItems: action.bound, syncSelectedItemsIfChanged: action.bound, }); this.syncSelectedItems(); } get filterKeyedItemsMap() { return new Map>( this.filterKeyedItems.map((e) => [e.key, e]), ); } get filter(): ArrayField { return this.toolbar.getPendingFilter(this._filter); } get hasPendingFilter() { return this._filter !== this.filter; } toKeyedItem = (item: V, index: number) => { const { filter } = this; const key = filter.itemKey(item); return { id: key, key, item, index, indexWithinPage: index, pageIndex: 0, }; }; get finalFilterKeyedItems() { const { _filter } = this; return _filter.value.map(this.toKeyedItem); } get finalFilterKeyedItemsMap() { return new Map>( this.finalFilterKeyedItems.map((e) => [e.key, e]), ); } get filterKeyedItems() { const { filter } = this; return filter.value.map(this.toKeyedItem); } syncSelectedItems() { const { select, events } = this; select.clear(); select.finalizeMany(this.filterKeyedItems); events.emit('syncSelectedItems'); } syncSelectedItemsIfChanged() { const { selectedItems, filter } = this; if (filter.value !== selectedItems) { this.syncSelectedItems(); } } get selectedItems() { const { select } = this; return select.selectedValues.map(({ item }) => item); } get reachedMax() { const { maxSelected, select } = this; return maxSelected != null && select.selectedCount >= maxSelected; } canToggle(key: string) { if (!this.reachedMax) { return true; } // at the cap: only existing keys, so they stay deselectable return this.select.selectedKeys.includes(key); } set(item: KeyedItem) { const { select } = this; if (!this.canToggle(item.key)) { return; } select.set(item); this.refreshIfChanged({ actionType: 'add', clickedValueKey: item.key }); } setSingle(item: KeyedItem) { const { select } = this; select.clear(); this.set(item); } clear() { const { select } = this; select.clear(); this.refreshIfChanged({ actionType: 'remove', clickedValueKey: '' }); } delete(item: KeyedItem) { const { select } = this; select.delete(item); this.refreshIfChanged({ actionType: 'remove', clickedValueKey: item.key }); } deleteKey(key: string) { const { select } = this; select.deleteKey(key); this.refreshIfChanged({ actionType: 'remove', clickedValueKey: key }); } refreshIfChanged( options: { actionType?: 'add' | 'remove'; clickedValueKey?: string } = {}, ) { const { selectedItems, filter } = this; if (filter == null) { return; } if (filter.value === selectedItems) { return; } filter.setValue(selectedItems, { emitEvents: ['change', 'beforeRefresh', 'refresh'], ...options, }); } close() { const { dropdown, events } = this; dropdown.setOff(); events.emit('close'); } open() { const { dropdown, events } = this; dropdown.setOn(); events.emit('open'); } closeAndChangeSelectedItems() { this.close(); const { selectedItems, filter } = this; if (filter.value === selectedItems) { return; } filter.setValue(selectedItems, { emitEvents: ['change', 'beforeRefresh', 'refresh'], actionType: 'add', }); } init() { const { filter } = this; const disposers = [ reaction(() => filter?.value, this.syncSelectedItemsIfChanged), ]; return () => { disposers.forEach((d) => d()); }; } }