UNPKG

@v4fire/client

Version:

V4Fire client core library

303 lines (240 loc) • 5.81 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ import type bSelect from 'form/b-select/b-select'; import type { ModEvent, SetModEvent } from 'super/i-input-text'; import { openedSelect } from 'form/b-select/const'; /** * Handler: value changing of a native component `<select>` * * @param component * @emits `actionChange(value: V)` */ export function nativeChange<C extends bSelect>(component: C): void { const { unsafe, unsafe: { block: $b, $refs: {input} } } = component; if ($b == null || !unsafe.native) { return; } const // @ts-ignore (TS 4.6.3) itemName = $b.getElSelector('item'), checkedItems = input.querySelectorAll(`${itemName}:checked`); let value; for (let i = 0; i < checkedItems.length; i++) { const el = checkedItems[i], id = el.getAttribute('data-id'), item = unsafe.indexes[String(id)]; if (item == null) { continue; } if (unsafe.multiple) { value ??= new Set(); value.add(item.value); } else { value = item.value; break; } } if (!Object.fastCompare(value, unsafe.field.get('valueStore'))) { unsafe.selectValue(value, true); unsafe.emit('actionChange', unsafe.value); } } /** * Handler: changing text of a component helper input * * @param component * @emits `actionChange(value: V)` */ export function textChange<C extends bSelect>(component: C): void { let {text} = component; if (component.unsafe.compiledMask != null) { text = text.replace(new RegExp(RegExp.escape(component.maskPlaceholder), 'g'), ''); } if (text !== '') { const rgxp = new RegExp(`^${RegExp.escape(text)}`, 'i'); for (let i = 0; i < component.items.length; i++) { const item = component.items[i]; if (item.label != null && rgxp.test(item.label)) { if (component.selectValue(item.value, true)) { component.emit('actionChange', component.value); } void component.open(); void component.unsafe.setScrollToMarkedOrSelectedItem(); return; } } } if (component.value !== undefined) { component.value = undefined; component.emit('actionChange', undefined); } void component.close(); } /** * Handler: typing text into a helper text input to search select options * * @param component * @param e * * @emits `actionChange(value: V)` */ export function searchInput<C extends bSelect>(component: C, e: InputEvent): void { const {unsafe} = component; const target = <HTMLInputElement>e.target; if (unsafe.compiledMask != null) { return; } unsafe.text = target.value; unsafe.onTextChange(); } /** * Handler: click to some item element * * @param component * @param itemEl * @emits `actionChange(value: V)` */ export function itemClick<C extends bSelect>(component: C, itemEl: CanUndef<Element>): void { void component.close(); if (itemEl == null || component.native) { return; } const id = itemEl.getAttribute('data-id'), item = component.unsafe.indexes[String(id)]; if (item == null) { return; } if (component.multiple) { component.toggleValue(item.value); } else { component.text = item.label ?? component.text; component.selectValue(item.value); } component.emit('actionChange', component.value); } /** * Handler: "navigation" over select items via "arrow" buttons * * @param component * @param e */ export async function itemsNavigate<C extends bSelect>(component: C, e: KeyboardEvent): Promise<void> { const {unsafe} = component; const validKeys = { ArrowUp: true, ArrowDown: true, Enter: true }; if (unsafe.native || validKeys[e.key] !== true || unsafe.mods.focused !== 'true') { if (e.key.length === 1) { await unsafe.focus(); } return; } e.preventDefault(); const {block: $b} = unsafe; if ($b == null) { return; } const getMarkedOrSelectedItem = () => // @ts-ignore (TS 4.6.3) $b.element('item', {marked: true}) ?? $b.element('item', {selected: true}); let currentItemEl = getMarkedOrSelectedItem(); const markItem = (itemEl: Nullable<Element>) => { if (currentItemEl != null) { $b.removeElMod(currentItemEl, 'item', 'marked'); } if (itemEl == null) { return false; } $b.setElMod(itemEl, 'item', 'marked', true); void unsafe.setScrollToMarkedOrSelectedItem(); return true; }; switch (e.key) { case 'Enter': unsafe.onItemClick(currentItemEl); break; case 'ArrowUp': if (currentItemEl?.previousElementSibling != null) { markItem(currentItemEl.previousElementSibling); } else { await unsafe.close(); } break; case 'ArrowDown': { if (unsafe.mods.opened !== 'true') { await unsafe.open(); if (unsafe.value != null) { return; } currentItemEl ??= getMarkedOrSelectedItem(); } markItem(currentItemEl?.nextElementSibling) || markItem($b.element('item')); break; } default: // Do nothing } } /** * @see [[iOpenToggle.onOpenedChange]] * @param component * @param e */ export function openedChange<C extends bSelect>(component: C, e: ModEvent | SetModEvent): void { const { unsafe, unsafe: {async: $a} } = component; if (unsafe.native) { return; } // Status: opened == false or opened == null if (e.type === 'set' && e.value === 'false' || e.type === 'remove') { if (openedSelect.link === unsafe) { openedSelect.link = null; } if (unsafe.mods.focused !== 'true') { $a.off({ group: 'navigation' }); } return; } $a.off({ group: 'navigation' }); if (!unsafe.multiple) { if (openedSelect.link != null) { openedSelect.link.close().catch(() => undefined); } openedSelect.link = unsafe; } $a.on(document, 'keydown', unsafe.onItemsNavigate.bind(unsafe), { group: 'navigation' }); }