UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

280 lines (277 loc) 12 kB
import { __rest } from '../../node_modules/tslib/tslib.es6.js'; import React, { forwardRef, useState, useRef, useId, useMemo, isValidElement } from 'react'; import PropTypes from 'prop-types'; import classNames from '../../_virtual/index.js'; import { useChipSet } from '../chip-set/useChipSet.js'; import '@popperjs/core'; import isRTL from '../../utils/isRTL.js'; import { useForkedRef } from '../../hooks/useForkedRef.js'; const resolveChipClassName = (chipClassName, value) => { if (!chipClassName) { return; } if (typeof chipClassName === 'function') { const resolvedClassName = chipClassName(value); return typeof resolvedClassName === 'string' ? resolvedClassName : undefined; } return chipClassName; }; const uniqueValues = (values) => [ ...new Set(values.map((value) => value.trim()).filter(Boolean)), ]; // Initial chip values can be supplied declaratively as CChip children (parity // with the vanilla ChipInput, which reads existing .chip elements on init). const valuesFromChildren = (children) => { const values = []; React.Children.forEach(children, (child) => { if (!isValidElement(child)) { return; } const { value, children: childContent } = child.props; const chipValue = value !== null && value !== void 0 ? value : (typeof childContent === 'string' ? childContent : undefined); if (chipValue) { values.push(chipValue); } }); return values; }; const CChipInput = forwardRef((_a, ref) => { var { children, className, chipClassName, createOnBlur = true, defaultValue = [], disabled, filter, id, label, maxChips = null, name, onAdd, onChange, onInput, onRemove, onSelect, placeholder = '', readOnly, removable = true, selectable, selectionMode, separator = ',', size, value } = _a, rest = __rest(_a, ["children", "className", "chipClassName", "createOnBlur", "defaultValue", "disabled", "filter", "id", "label", "maxChips", "name", "onAdd", "onChange", "onInput", "onRemove", "onSelect", "placeholder", "readOnly", "removable", "selectable", "selectionMode", "separator", "size", "value"]); const isControlled = value !== undefined; const [_values, setValues] = useState(() => uniqueValues(defaultValue.length > 0 ? defaultValue : valuesFromChildren(children))); const [inputValue, setInputValue] = useState(''); const inputRef = useRef(null); const generatedName = useId(); const values = useMemo(() => uniqueValues(isControlled ? value : _values), [isControlled, value, _values]); // Build CChipInput on the same engine as CChipSet: selection coordination, // roving focus, and chip prop injection all live in useChipSet. CChipInput // owns the chip list and adds the text-input layer on top. const { rootRef, clearSelection, getFocusableChips, handleKeyDown, renderChipsFromData } = useChipSet({ ariaRemoveLabel: undefined, disabled, filter, removable: Boolean(removable && !disabled && !readOnly), selectable, selectionMode, restoreFocusOnRemove: false, onSelectionChange: onSelect, onRemoveChip: (chipValue) => remove(chipValue), }); const forkedRef = useForkedRef(ref, rootRef); const emitValuesChange = (nextValues) => { if (!isControlled) { setValues(nextValues); } onChange === null || onChange === void 0 ? void 0 : onChange(nextValues); }; const canAddMore = maxChips === null || values.length < maxChips; const add = (rawValue) => { if (disabled || readOnly) { return false; } const normalizedValue = String(rawValue).trim(); if (!normalizedValue || values.includes(normalizedValue) || !canAddMore) { return false; } const nextValues = [...values, normalizedValue]; emitValuesChange(nextValues); onAdd === null || onAdd === void 0 ? void 0 : onAdd(normalizedValue); return true; }; const remove = (valueToRemove) => { var _a; if (disabled || readOnly) { return false; } if (!values.includes(valueToRemove)) { return false; } // Selection is cleaned up by useChipSet; here we just drop the value. emitValuesChange(values.filter((item) => item !== valueToRemove)); onRemove === null || onRemove === void 0 ? void 0 : onRemove(valueToRemove); (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); return true; }; const createFromInput = () => { if (add(inputValue)) { setInputValue(''); } }; const focusLastChip = () => { var _a; const chips = getFocusableChips(); (_a = chips[chips.length - 1]) === null || _a === void 0 ? void 0 : _a.focus(); }; const handleInputKeyDown = (event) => { switch (event.key) { case 'Enter': { event.preventDefault(); createFromInput(); break; } case 'Backspace': case 'Delete': { if (inputValue === '') { event.preventDefault(); focusLastChip(); } break; } case 'ArrowLeft': case 'ArrowRight': { // The arrow pointing toward the chips (left in LTR, right in RTL) jumps // to the last chip when the caret is at the start of the input. const towardChipsKey = isRTL(rootRef.current) ? 'ArrowRight' : 'ArrowLeft'; if (event.key === towardChipsKey && event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === 0) { event.preventDefault(); focusLastChip(); } break; } case 'Escape': { setInputValue(''); event.currentTarget.blur(); break; } // No default } }; const handleInputChange = (value) => { if (disabled || readOnly) { return; } if (separator && value.includes(separator)) { const parts = value.split(separator); const chipsToAdd = uniqueValues(parts.slice(0, -1)); const nextValues = [...values]; for (const chipValue of chipsToAdd) { if (maxChips !== null && nextValues.length >= maxChips) { break; } if (!nextValues.includes(chipValue)) { nextValues.push(chipValue); onAdd === null || onAdd === void 0 ? void 0 : onAdd(chipValue); } } if (nextValues.length !== values.length) { emitValuesChange(nextValues); } const tail = parts[parts.length - 1] || ''; setInputValue(tail); onInput === null || onInput === void 0 ? void 0 : onInput(tail); return; } setInputValue(value); onInput === null || onInput === void 0 ? void 0 : onInput(value); }; const handlePaste = (event) => { if (disabled || readOnly || !separator) { return; } const pastedData = event.clipboardData.getData('text'); if (!pastedData.includes(separator)) { return; } event.preventDefault(); const chipsToAdd = uniqueValues(pastedData.split(separator)); const nextValues = [...values]; for (const chipValue of chipsToAdd) { if (maxChips !== null && nextValues.length >= maxChips) { break; } if (!nextValues.includes(chipValue)) { nextValues.push(chipValue); onAdd === null || onAdd === void 0 ? void 0 : onAdd(chipValue); } } if (nextValues.length !== values.length) { emitValuesChange(nextValues); } setInputValue(''); onInput === null || onInput === void 0 ? void 0 : onInput(''); }; const handleInputBlur = (event) => { var _a; if (!createOnBlur) { return; } if ((_a = event.relatedTarget) === null || _a === void 0 ? void 0 : _a.closest('.chip')) { return; } createFromInput(); }; const handleContainerKeyDown = (event) => { var _a, _b; if (event.target === inputRef.current) { return; } // The arrow past the last chip moves focus into the text field (mirrored in RTL). if (event.key === (isRTL(rootRef.current) ? 'ArrowLeft' : 'ArrowRight')) { const chips = getFocusableChips(); const lastChip = chips[chips.length - 1]; if (lastChip === null || lastChip === void 0 ? void 0 : lastChip.contains(event.target)) { event.preventDefault(); (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); return; } } if (handleKeyDown(event)) { return; } if (event.key.length === 1) { (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus(); } }; const handleContainerClick = (event) => { var _a; if (event.target === rootRef.current) { (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); } }; const inputSize = Math.max(placeholder.length, inputValue.length, 1); return (React.createElement("div", Object.assign({ className: classNames('chip-input', { 'chip-input-sm': size === 'sm', 'chip-input-lg': size === 'lg', disabled, }, className), "aria-disabled": disabled ? true : undefined, "aria-readonly": readOnly ? true : undefined, onClick: handleContainerClick, onKeyDown: handleContainerKeyDown }, rest, { ref: forkedRef }), label && (React.createElement("label", { className: "chip-input-label", htmlFor: id }, label)), renderChipsFromData(values.map((chipValue) => ({ value: chipValue, label: chipValue, className: resolveChipClassName(chipClassName, chipValue), ariaRemoveLabel: `Remove ${chipValue}`, }))), React.createElement("input", { type: "text", id: id, className: "chip-input-field", disabled: disabled, readOnly: Boolean(!disabled && readOnly), placeholder: placeholder, size: inputSize, value: inputValue, onBlur: handleInputBlur, onChange: (event) => handleInputChange(event.target.value), onKeyDown: handleInputKeyDown, onPaste: handlePaste, onFocus: clearSelection, ref: inputRef }), React.createElement("input", { type: "hidden", name: name !== null && name !== void 0 ? name : generatedName, value: values.join(',') }))); }); CChipInput.propTypes = { children: PropTypes.node, chipClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), className: PropTypes.string, createOnBlur: PropTypes.bool, defaultValue: PropTypes.array, disabled: PropTypes.bool, filter: PropTypes.bool, id: PropTypes.string, label: PropTypes.node, maxChips: PropTypes.number, name: PropTypes.string, onAdd: PropTypes.func, onChange: PropTypes.func, onInput: PropTypes.func, onRemove: PropTypes.func, onSelect: PropTypes.func, placeholder: PropTypes.string, readOnly: PropTypes.bool, removable: PropTypes.bool, selectable: PropTypes.bool, selectionMode: PropTypes.oneOf(['single', 'multiple']), separator: PropTypes.string, size: PropTypes.oneOf(['sm', 'lg']), value: PropTypes.array, }; CChipInput.displayName = 'CChipInput'; export { CChipInput }; //# sourceMappingURL=CChipInput.js.map