UNPKG

@coreui/vue

Version:

UI Components Library for Vue.js

113 lines (110 loc) 4.48 kB
import { ref, computed, provide, nextTick } from 'vue'; import { SELECTOR_CHIP_FOCUSABLE } from '../chip/const.js'; import isRTL from '../../utils/isRTL.js'; const chipSetContextKey = Symbol('CChipSet'); /** * Shared chip-set engine for CChipSet and CChipInput — the Vue equivalent of the * vanilla `ChipInput extends ChipSet`. It owns selection coordination * (single/multiple, controlled-or-uncontrolled), roving focus, and the * focus-a-neighbor-after-removal behavior, and `provide`s a context that CChip * injects. Existence of the chips stays with the caller. */ const useChipSet = (options) => { const { config, selectionMode = () => 'multiple', selected = () => undefined, defaultSelected = [], restoreFocusOnRemove = true, onSelectionChange, onRemove, } = options; const rootRef = ref(); const internalSelected = ref(defaultSelected); const selectedValues = computed(() => { const controlled = selected(); return controlled === undefined ? internalSelected.value : controlled; }); const getFocusableChips = () => [ ...(rootRef.value?.querySelectorAll(SELECTOR_CHIP_FOCUSABLE) ?? []), ]; const emitSelection = (next) => { if (selected() === undefined) { internalSelected.value = next; } onSelectionChange?.(next); }; const toggleSelected = (value, next) => { const isSelected = selectedValues.value.includes(value); if (next === isSelected) { return; } if (next) { // Single selection mode keeps only the freshly selected chip. emitSelection(selectionMode() === 'single' ? [value] : [...selectedValues.value, value]); return; } emitSelection(selectedValues.value.filter((item) => item !== value)); }; const clearSelection = () => { if (selectedValues.value.length > 0) { emitSelection([]); } }; const removeChip = (value) => { if (restoreFocusOnRemove) { const chips = getFocusableChips(); const index = chips.findIndex((chip) => chip.dataset.coreuiChipValue === value); // Prefer the next chip, fall back to the previous one at the end. const neighbor = chips[index + 1] ?? chips[index - 1] ?? null; const neighborValue = neighbor?.dataset.coreuiChipValue ?? null; if (neighborValue !== null) { nextTick(() => { rootRef.value ?.querySelector(`[data-coreui-chip-value="${neighborValue}"]`) ?.focus(); }); } } emitSelection(selectedValues.value.filter((item) => item !== value)); onRemove?.(value); }; // Roving focus: arrow keys move between chips and Home/End jump to the edges, // with no cycling. Returns true when the key was handled. const handleKeydown = (event) => { const chip = event.target.closest(SELECTOR_CHIP_FOCUSABLE); if (!chip) { return false; } const chips = getFocusableChips(); const index = chips.indexOf(chip); // In RTL the visual direction is mirrored, so left/right swap. const rtl = isRTL(rootRef.value); switch (event.key) { case 'ArrowLeft': { event.preventDefault(); chips[rtl ? index + 1 : index - 1]?.focus(); return true; } case 'ArrowRight': { event.preventDefault(); chips[rtl ? index - 1 : index + 1]?.focus(); return true; } case 'Home': { event.preventDefault(); chips[0]?.focus(); return true; } case 'End': { event.preventDefault(); chips[chips.length - 1]?.focus(); return true; } default: { return false; } } }; provide(chipSetContextKey, { config, isSelected: (value) => selectedValues.value.includes(value), toggleSelected: (value, next) => toggleSelected(value, next), removeChip: (value) => removeChip(value), }); return { rootRef, selectedValues, clearSelection, getFocusableChips, handleKeydown }; }; export { chipSetContextKey, useChipSet }; //# sourceMappingURL=useChipSet.js.map