@coreui/react-pro
Version:
UI Components Library for React.js
162 lines (159 loc) • 8.06 kB
JavaScript
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