"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState")); var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning")); var React = _interopRequireWildcard(require("react")); var _BaseSelect = _interopRequireWildcard(require("./BaseSelect")); var _OptGroup = _interopRequireDefault(require("./OptGroup")); var _Option = _interopRequireDefault(require("./Option")); var _OptionList = _interopRequireDefault(require("./OptionList")); var _SelectContext = _interopRequireDefault(require("./SelectContext")); var _useCache = _interopRequireDefault(require("./hooks/useCache")); var _useFilterOptions = _interopRequireDefault(require("./hooks/useFilterOptions")); var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId")); var _useOptions = _interopRequireDefault(require("./hooks/useOptions")); var _useRefFunc = _interopRequireDefault(require("./hooks/useRefFunc")); var _commonUtil = require("./utils/commonUtil"); var _valueUtil = require("./utils/valueUtil"); var _warningPropsUtil = _interopRequireWildcard(require("./utils/warningPropsUtil")); var _useSearchConfig = _interopRequireDefault(require("./hooks/useSearchConfig")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /** * To match accessibility requirement, we always provide an input in the component. * Other element will not set `tabIndex` to avoid `onBlur` sequence problem. * For focused select, we set `aria-live="polite"` to update the accessibility content. * * ref: * - keyboard: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role#Keyboard_interactions * * New api: * - listHeight * - listItemHeight * - component * * Remove deprecated api: * - multiple * - tags * - combobox * - firstActiveValue * - dropdownMenuStyle * - openClassName (Not list in api) * * Update: * - `backfill` only support `combobox` mode * - `combobox` mode not support `labelInValue` since it's meaningless * - `getInputElement` only support `combobox` mode * - `onChange` return OptionData instead of ReactNode * - `filterOption` `onChange` `onSelect` accept OptionData instead of ReactNode * - `combobox` mode trigger `onChange` will get `undefined` if no `value` match in Option * - `combobox` mode not support `optionLabelProp` */ const OMIT_DOM_PROPS = ['inputValue']; function isRawValue(value) { return !value || typeof value !== 'object'; } const Select = /*#__PURE__*/React.forwardRef((props, ref) => { const { id, mode, prefixCls = 'rc-select', backfill, fieldNames, // Search showSearch, searchValue: legacySearchValue, onSearch: legacyOnSearch, autoClearSearchValue: legacyAutoClearSearchValue, filterOption: legacyFilterOption, optionFilterProp: legacyOptionFilterProp, filterSort: legacyFilterSort, // Select onSelect, onDeselect, onActive, popupMatchSelectWidth = true, optionLabelProp, options, optionRender, children, defaultActiveFirstOption, menuItemSelectedIcon, virtual, direction, listHeight = 200, listItemHeight = 20, labelRender, // Value value, defaultValue, labelInValue, onChange, maxCount, classNames, styles, ...restProps } = props; const searchProps = { searchValue: legacySearchValue, onSearch: legacyOnSearch, autoClearSearchValue: legacyAutoClearSearchValue, filterOption: legacyFilterOption, optionFilterProp: legacyOptionFilterProp, filterSort: legacyFilterSort }; const [mergedShowSearch, searchConfig] = (0, _useSearchConfig.default)(showSearch, searchProps, mode); const { filterOption, searchValue, optionFilterProp, filterSort, onSearch, autoClearSearchValue = true } = searchConfig; const normalizedOptionFilterProp = React.useMemo(() => { if (!optionFilterProp) return []; return Array.isArray(optionFilterProp) ? optionFilterProp : [optionFilterProp]; }, [optionFilterProp]); const mergedId = (0, _useId.default)(id); const multiple = (0, _BaseSelect.isMultiple)(mode); const childrenAsData = !!(!options && children); const mergedFilterOption = React.useMemo(() => { if (filterOption === undefined && mode === 'combobox') { return false; } return filterOption; }, [filterOption, mode]); // ========================= FieldNames ========================= const mergedFieldNames = React.useMemo(() => (0, _valueUtil.fillFieldNames)(fieldNames, childrenAsData), /* eslint-disable react-hooks/exhaustive-deps */ [ // We stringify fieldNames to avoid unnecessary re-renders. JSON.stringify(fieldNames), childrenAsData] /* eslint-enable react-hooks/exhaustive-deps */); // =========================== Search =========================== const [internalSearchValue, setSearchValue] = (0, _useControlledState.default)('', searchValue); const mergedSearchValue = internalSearchValue || ''; // =========================== Option =========================== const parsedOptions = (0, _useOptions.default)(options, children, mergedFieldNames, normalizedOptionFilterProp, optionLabelProp); const { valueOptions, labelOptions, options: mergedOptions } = parsedOptions; // ========================= Wrap Value ========================= const convert2LabelValues = React.useCallback(draftValues => { // Convert to array const valueList = (0, _commonUtil.toArray)(draftValues); // Convert to labelInValue type return valueList.map(val => { let rawValue; let rawLabel; let rawDisabled; let rawTitle; // Fill label & value if (isRawValue(val)) { rawValue = val; } else { rawLabel = val.label; rawValue = val.value; } const option = valueOptions.get(rawValue); if (option) { // Fill missing props if (rawLabel === undefined) rawLabel = option?.[optionLabelProp || mergedFieldNames.label]; rawDisabled = option?.disabled; rawTitle = option?.title; // Warning if label not same as provided if (process.env.NODE_ENV !== 'production' && !optionLabelProp) { const optionLabel = option?.[mergedFieldNames.label]; if (optionLabel !== undefined && ! /*#__PURE__*/React.isValidElement(optionLabel) && ! /*#__PURE__*/React.isValidElement(rawLabel) && optionLabel !== rawLabel) { (0, _warning.default)(false, '`label` of `value` is not same as `label` in Select options.'); } } } return { label: rawLabel, value: rawValue, key: rawValue, disabled: rawDisabled, title: rawTitle }; }); }, [mergedFieldNames, optionLabelProp, valueOptions]); // =========================== Values =========================== const [internalValue, setInternalValue] = (0, _useControlledState.default)(defaultValue, value); // Merged value with LabelValueType const rawLabeledValues = React.useMemo(() => { const newInternalValue = multiple && internalValue === null ? [] : internalValue; const values = convert2LabelValues(newInternalValue); // combobox no need save value when it's no value (exclude value equal 0) if (mode === 'combobox' && (0, _commonUtil.isComboNoValue)(values[0]?.value)) { return []; } return values; }, [internalValue, convert2LabelValues, mode, multiple]); // Fill label with cache to avoid option remove const [mergedValues, getMixedOption] = (0, _useCache.default)(rawLabeledValues, valueOptions); const displayValues = React.useMemo(() => { // `null` need show as placeholder instead // https://github.com/ant-design/ant-design/issues/25057 if (!mode && mergedValues.length === 1) { const firstValue = mergedValues[0]; if (firstValue.value === null && (firstValue.label === null || firstValue.label === undefined)) { return []; } } return mergedValues.map(item => ({ ...item, label: (typeof labelRender === 'function' ? labelRender(item) : item.label) ?? item.value })); }, [mode, mergedValues, labelRender]); /** Convert `displayValues` to raw value type set */ const rawValues = React.useMemo(() => new Set(mergedValues.map(val => val.value)), [mergedValues]); React.useEffect(() => { if (mode === 'combobox') { const strValue = mergedValues[0]?.value; setSearchValue((0, _commonUtil.hasValue)(strValue) ? String(strValue) : ''); } }, [mergedValues]); // ======================= Display Option ======================= // Create a placeholder item if not exist in `options` const createTagOption = (0, _useRefFunc.default)((val, label) => { const mergedLabel = label ?? val; return { [mergedFieldNames.value]: val, [mergedFieldNames.label]: mergedLabel }; }); // Fill tag as option if mode is `tags` const filledTagOptions = React.useMemo(() => { if (mode !== 'tags') { return mergedOptions; } // >>> Tag mode const cloneOptions = [...mergedOptions]; // Check if value exist in options (include new patch item) const existOptions = val => valueOptions.has(val); // Fill current value as option [...mergedValues].sort((a, b) => a.value < b.value ? -1 : 1).forEach(item => { const val = item.value; if (!existOptions(val)) { cloneOptions.push(createTagOption(val, item.label)); } }); return cloneOptions; }, [createTagOption, mergedOptions, valueOptions, mergedValues, mode]); const filteredOptions = (0, _useFilterOptions.default)(filledTagOptions, mergedFieldNames, mergedSearchValue, mergedFilterOption, normalizedOptionFilterProp); // Fill options with search value if needed const filledSearchOptions = React.useMemo(() => { const hasItemMatchingSearch = item => { if (normalizedOptionFilterProp.length) { return normalizedOptionFilterProp.some(prop => item?.[prop] === mergedSearchValue); } return item?.value === mergedSearchValue; }; if (mode !== 'tags' || !mergedSearchValue || filteredOptions.some(item => hasItemMatchingSearch(item))) { return filteredOptions; } // ignore when search value equal select input value if (filteredOptions.some(item => item[mergedFieldNames.value] === mergedSearchValue)) { return filteredOptions; } // Fill search value as option return [createTagOption(mergedSearchValue), ...filteredOptions]; }, [createTagOption, normalizedOptionFilterProp, mode, filteredOptions, mergedSearchValue, mergedFieldNames]); const sorter = inputOptions => { const sortedOptions = [...inputOptions].sort((a, b) => filterSort(a, b, { searchValue: mergedSearchValue })); return sortedOptions.map(item => { if (Array.isArray(item.options)) { return { ...item, options: item.options.length > 0 ? sorter(item.options) : item.options }; } return item; }); }; const orderedFilteredOptions = React.useMemo(() => { if (!filterSort) { return filledSearchOptions; } return sorter(filledSearchOptions); }, [filledSearchOptions, filterSort, mergedSearchValue]); const displayOptions = React.useMemo(() => (0, _valueUtil.flattenOptions)(orderedFilteredOptions, { fieldNames: mergedFieldNames, childrenAsData }), [orderedFilteredOptions, mergedFieldNames, childrenAsData]); // =========================== Change =========================== const triggerChange = values => { const labeledValues = convert2LabelValues(values); setInternalValue(labeledValues); if (onChange && ( // Trigger event only when value changed labeledValues.length !== mergedValues.length || labeledValues.some((newVal, index) => mergedValues[index]?.value !== newVal?.value))) { const returnValues = labelInValue ? labeledValues.map(({ label: l, value: v }) => ({ label: l, value: v })) : labeledValues.map(v => v.value); const returnOptions = labeledValues.map(v => (0, _valueUtil.injectPropsWithOption)(getMixedOption(v.value))); onChange( // Value multiple ? returnValues : returnValues[0], // Option multiple ? returnOptions : returnOptions[0]); } }; // ======================= Accessibility ======================== const [activeValue, setActiveValue] = React.useState(null); const [accessibilityIndex, setAccessibilityIndex] = React.useState(0); const mergedDefaultActiveFirstOption = defaultActiveFirstOption !== undefined ? defaultActiveFirstOption : mode !== 'combobox'; const activeEventRef = React.useRef(); const onActiveValue = React.useCallback((active, index, { source = 'keyboard' } = {}) => { setAccessibilityIndex(index); if (backfill && mode === 'combobox' && active !== null && source === 'keyboard') { setActiveValue(String(active)); } // Active will call multiple times. // We only need trigger the last one. const promise = Promise.resolve().then(() => { if (activeEventRef.current === promise) { onActive?.(active); } }); activeEventRef.current = promise; }, [backfill, mode, onActive]); // ========================= OptionList ========================= const triggerSelect = (val, selected, type) => { const getSelectEnt = () => { const option = getMixedOption(val); return [labelInValue ? { label: option?.[mergedFieldNames.label], value: val } : val, (0, _valueUtil.injectPropsWithOption)(option)]; }; if (selected && onSelect) { const [wrappedValue, option] = getSelectEnt(); onSelect(wrappedValue, option); } else if (!selected && onDeselect && type !== 'clear') { const [wrappedValue, option] = getSelectEnt(); onDeselect(wrappedValue, option); } }; // Used for OptionList selection const onInternalSelect = (0, _useRefFunc.default)((val, info) => { let cloneValues; // Single mode always trigger select only with option list const mergedSelect = multiple ? info.selected : true; if (mergedSelect) { cloneValues = multiple ? [...mergedValues, val] : [val]; } else { cloneValues = mergedValues.filter(v => v.value !== val); } triggerChange(cloneValues); triggerSelect(val, mergedSelect); // Clean search value if single or configured if (mode === 'combobox') { setActiveValue(''); } else if (!_BaseSelect.isMultiple || autoClearSearchValue) { setSearchValue(''); setActiveValue(''); } }); // ======================= Display Change ======================= // BaseSelect display values change const onDisplayValuesChange = (nextValues, info) => { triggerChange(nextValues); const { type, values } = info; if (type === 'remove' || type === 'clear') { values.forEach(item => { triggerSelect(item.value, false, type); }); } }; // =========================== Search =========================== const onInternalSearch = (searchText, info) => { setSearchValue(searchText); setActiveValue(null); // [Submit] Tag mode should flush input if (info.source === 'submit') { const formatted = (searchText || '').trim(); // prevent empty tags from appearing when you click the Enter button if (formatted) { const newRawValues = Array.from(new Set([...rawValues, formatted])); triggerChange(newRawValues); triggerSelect(formatted, true); setSearchValue(''); } return; } if (info.source !== 'blur') { if (mode === 'combobox') { triggerChange(searchText); } onSearch?.(searchText); } }; const onInternalSearchSplit = words => { let patchValues = words; if (mode !== 'tags') { patchValues = words.map(word => { const opt = labelOptions.get(word); return opt?.value; }).filter(val => val !== undefined); } const newRawValues = Array.from(new Set([...rawValues, ...patchValues])); triggerChange(newRawValues); newRawValues.forEach(newRawValue => { triggerSelect(newRawValue, true); }); }; // ========================== Context =========================== const selectContext = React.useMemo(() => { const realVirtual = virtual !== false && popupMatchSelectWidth !== false; return { ...parsedOptions, flattenOptions: displayOptions, onActiveValue, defaultActiveFirstOption: mergedDefaultActiveFirstOption, onSelect: onInternalSelect, menuItemSelectedIcon, rawValues, fieldNames: mergedFieldNames, virtual: realVirtual, direction, listHeight, listItemHeight, childrenAsData, maxCount, optionRender, classNames, styles }; }, [maxCount, parsedOptions, displayOptions, onActiveValue, mergedDefaultActiveFirstOption, onInternalSelect, menuItemSelectedIcon, rawValues, mergedFieldNames, virtual, popupMatchSelectWidth, direction, listHeight, listItemHeight, childrenAsData, optionRender, classNames, styles]); // ========================== Warning =========================== if (process.env.NODE_ENV !== 'production') { (0, _warningPropsUtil.default)(props); (0, _warningPropsUtil.warningNullOptions)(mergedOptions, mergedFieldNames); } // ============================================================== // == Render == // ============================================================== return /*#__PURE__*/React.createElement(_SelectContext.default.Provider, { value: selectContext }, /*#__PURE__*/React.createElement(_BaseSelect.default, _extends({}, restProps, { // >>> MISC id: mergedId, prefixCls: prefixCls, ref: ref, omitDomProps: OMIT_DOM_PROPS, mode: mode // >>> Style , classNames: classNames, styles: styles // >>> Values , displayValues: displayValues, onDisplayValuesChange: onDisplayValuesChange, maxCount: maxCount // >>> Trigger , direction: direction // >>> Search , showSearch: mergedShowSearch, searchValue: mergedSearchValue, onSearch: onInternalSearch, autoClearSearchValue: autoClearSearchValue, onSearchSplit: onInternalSearchSplit, popupMatchSelectWidth: popupMatchSelectWidth // >>> OptionList , OptionList: _OptionList.default, emptyOptions: !displayOptions.length // >>> Accessibility , activeValue: activeValue, activeDescendantId: `${mergedId}_list_${accessibilityIndex}` }))); }); if (process.env.NODE_ENV !== 'production') { Select.displayName = 'Select'; } const TypedSelect = Select; TypedSelect.Option = _Option.default; TypedSelect.OptGroup = _OptGroup.default; var _default = exports.default = TypedSelect;