UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

162 lines (159 loc) 8.06 kB
import { __rest } from '../../node_modules/tslib/tslib.es6.js'; import React, { useRef, useState, useEffect, isValidElement } from 'react'; import { CChip } from '../chip/CChip.js'; import { SELECTOR_CHIP_FOCUSABLE } from '../chip/const.js'; import { resolveChipValue } from './utils.js'; import isRTL from '../../utils/isRTL.js'; // Build a CChip element from a data item (string or descriptor). const buildChip = (item) => { if (typeof item === 'string') { return React.createElement(CChip, { key: item, value: item }, item); } const { label, value } = item, chipProps = __rest(item, ["label", "value"]); return React.createElement(CChip, Object.assign({ key: value, value }, chipProps), label !== null && label !== void 0 ? label : value); }; /** * The shared chip-set engine. Both `CChipSet` and `CChipInput` build on it — the * React equivalent of the vanilla `ChipInput extends ChipSet`. It owns selection * coordination (single/multiple, controlled-or-uncontrolled), roving focus, the * focus-a-neighbor-after-removal behavior, and the per-chip prop injection. * Existence of the chips stays with the caller (its children / value list). */ const useChipSet = (options) => { const { ariaRemoveLabel, defaultSelected = [], disabled, filter, removable, removeIcon, restoreFocusOnRemove = true, selectable, selected, selectedIcon, selectionMode = 'multiple', onRemoveChip, onSelectionChange, } = options; const rootRef = useRef(null); const pendingFocusValue = useRef(null); const isControlled = selected !== undefined; const [_selected, setSelected] = useState(defaultSelected); // In controlled mode the selected prop drives selection directly; _selected is // only read when uncontrolled, so no syncing effect is needed. const selectedValues = isControlled ? selected : _selected; // Focus the saved neighbor once the removed chip has left the DOM. useEffect(() => { var _a, _b; if (pendingFocusValue.current === null) { return; } const selector = `[data-coreui-chip-value="${pendingFocusValue.current}"]`; (_b = (_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(selector)) === null || _b === void 0 ? void 0 : _b.focus(); pendingFocusValue.current = null; }); const getFocusableChips = () => { var _a, _b; return [ ...((_b = (_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(SELECTOR_CHIP_FOCUSABLE)) !== null && _b !== void 0 ? _b : []), ]; }; const emitSelection = (next) => { if (!isControlled) { setSelected(next); } onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange(next); }; const updateSelection = (chipValue, nextSelected) => { const isSelected = selectedValues.includes(chipValue); if (nextSelected === isSelected) { return; } if (nextSelected) { // Single selection mode keeps only the freshly selected chip. emitSelection(selectionMode === 'single' ? [chipValue] : [...selectedValues, chipValue]); return; } emitSelection(selectedValues.filter((item) => item !== chipValue)); }; const clearSelection = () => { if (selectedValues.length > 0) { emitSelection([]); } }; const removeChip = (chipValue) => { var _a, _b, _c; if (restoreFocusOnRemove) { const chips = getFocusableChips(); const index = chips.findIndex((chip) => chip.dataset.coreuiChipValue === chipValue); // Prefer the next chip, fall back to the previous one at the end. const neighbor = (_b = (_a = chips[index + 1]) !== null && _a !== void 0 ? _a : chips[index - 1]) !== null && _b !== void 0 ? _b : null; pendingFocusValue.current = (_c = neighbor === null || neighbor === void 0 ? void 0 : neighbor.dataset.coreuiChipValue) !== null && _c !== void 0 ? _c : null; } emitSelection(selectedValues.filter((item) => item !== chipValue)); onRemoveChip === null || onRemoveChip === void 0 ? void 0 : onRemoveChip(chipValue); }; // 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) => { var _a, _b, _c, _d; 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.current); switch (event.key) { case 'ArrowLeft': { event.preventDefault(); (_a = chips[rtl ? index + 1 : index - 1]) === null || _a === void 0 ? void 0 : _a.focus(); return true; } case 'ArrowRight': { event.preventDefault(); (_b = chips[rtl ? index - 1 : index + 1]) === null || _b === void 0 ? void 0 : _b.focus(); return true; } case 'Home': { event.preventDefault(); (_c = chips[0]) === null || _c === void 0 ? void 0 : _c.focus(); return true; } case 'End': { event.preventDefault(); (_d = chips[chips.length - 1]) === null || _d === void 0 ? void 0 : _d.focus(); return true; } default: { return false; } } }; const renderChips = (children) => React.Children.map(children, (child, index) => { var _a, _b, _c, _d, _e, _f, _g; if (!isValidElement(child)) { return child; } const chipValue = resolveChipValue(child, index); const childSelectable = (_a = child.props.selectable) !== null && _a !== void 0 ? _a : selectable; const childFilter = (_b = child.props.filter) !== null && _b !== void 0 ? _b : filter; const childRemovable = (_c = child.props.removable) !== null && _c !== void 0 ? _c : removable; const isCoordinated = Boolean(childSelectable || childFilter); return React.cloneElement(child, Object.assign(Object.assign({ ariaRemoveLabel: (_d = child.props.ariaRemoveLabel) !== null && _d !== void 0 ? _d : ariaRemoveLabel, disabled: (_e = child.props.disabled) !== null && _e !== void 0 ? _e : disabled, filter: childFilter, removable: childRemovable, removeIcon: (_f = child.props.removeIcon) !== null && _f !== void 0 ? _f : removeIcon, selectable: childSelectable, selectedIcon: (_g = child.props.selectedIcon) !== null && _g !== void 0 ? _g : selectedIcon }, (isCoordinated && { selected: selectedValues.includes(chipValue), onSelectedChange: (nextSelected, event) => { var _a, _b; updateSelection(chipValue, nextSelected); (_b = (_a = child.props).onSelectedChange) === null || _b === void 0 ? void 0 : _b.call(_a, nextSelected, event); }, })), (childRemovable && { onRemove: (event) => { var _a, _b; removeChip(chipValue); (_b = (_a = child.props).onRemove) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, }))); }); // Build CChip elements from data, then run them through the same transform — // used by CChipSet's `chips` prop and by CChipInput's value list. const renderChipsFromData = (items) => renderChips(items.map((item) => buildChip(item))); return { rootRef, selectedValues, clearSelection, getFocusableChips, handleKeyDown, renderChips, renderChipsFromData, }; }; export { useChipSet }; //# sourceMappingURL=useChipSet.js.map