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