import _extends from "@babel/runtime/helpers/esm/extends"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; import _typeof from "@babel/runtime/helpers/esm/typeof"; var _excluded = ["id", "prefixCls", "value", "defaultValue", "onChange", "onSelect", "onDeselect", "searchValue", "inputValue", "onSearch", "autoClearSearchValue", "filterTreeNode", "treeNodeFilterProp", "showCheckedStrategy", "treeNodeLabelProp", "multiple", "treeCheckable", "treeCheckStrictly", "labelInValue", "fieldNames", "treeDataSimpleMode", "treeData", "children", "loadData", "treeLoadedKeys", "onTreeLoad", "treeDefaultExpandAll", "treeExpandedKeys", "treeDefaultExpandedKeys", "onTreeExpand", "treeExpandAction", "virtual", "listHeight", "listItemHeight", "listItemScrollOffset", "onDropdownVisibleChange", "dropdownMatchSelectWidth", "treeLine", "treeIcon", "showTreeIcon", "switcherIcon", "treeMotion", "treeTitleRender", "onPopupScroll"]; import { BaseSelect } from 'rc-select'; import useId from "rc-select/es/hooks/useId"; import { conductCheck } from "rc-tree/es/utils/conductUtil"; import useMergedState from "rc-util/es/hooks/useMergedState"; import warning from "rc-util/es/warning"; import * as React from 'react'; import useCache from "./hooks/useCache"; import useCheckedKeys from "./hooks/useCheckedKeys"; import useDataEntities from "./hooks/useDataEntities"; import useFilterTreeData from "./hooks/useFilterTreeData"; import useRefFunc from "./hooks/useRefFunc"; import useTreeData from "./hooks/useTreeData"; import LegacyContext from "./LegacyContext"; import OptionList from "./OptionList"; import TreeNode from "./TreeNode"; import TreeSelectContext from "./TreeSelectContext"; import { fillAdditionalInfo, fillLegacyProps } from "./utils/legacyUtil"; import { formatStrategyValues, SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from "./utils/strategyUtil"; import { fillFieldNames, isNil, toArray } from "./utils/valueUtil"; import warningProps from "./utils/warningPropsUtil"; /** @deprecated This is only used for legacy compatible. Not works on new code. */ function isRawValue(value) { return !value || _typeof(value) !== 'object'; } var TreeSelect = /*#__PURE__*/React.forwardRef(function (props, ref) { var id = props.id, _props$prefixCls = props.prefixCls, prefixCls = _props$prefixCls === void 0 ? 'rc-tree-select' : _props$prefixCls, value = props.value, defaultValue = props.defaultValue, onChange = props.onChange, onSelect = props.onSelect, onDeselect = props.onDeselect, searchValue = props.searchValue, inputValue = props.inputValue, onSearch = props.onSearch, _props$autoClearSearc = props.autoClearSearchValue, autoClearSearchValue = _props$autoClearSearc === void 0 ? true : _props$autoClearSearc, filterTreeNode = props.filterTreeNode, _props$treeNodeFilter = props.treeNodeFilterProp, treeNodeFilterProp = _props$treeNodeFilter === void 0 ? 'value' : _props$treeNodeFilter, showCheckedStrategy = props.showCheckedStrategy, treeNodeLabelProp = props.treeNodeLabelProp, multiple = props.multiple, treeCheckable = props.treeCheckable, treeCheckStrictly = props.treeCheckStrictly, labelInValue = props.labelInValue, fieldNames = props.fieldNames, treeDataSimpleMode = props.treeDataSimpleMode, treeData = props.treeData, children = props.children, loadData = props.loadData, treeLoadedKeys = props.treeLoadedKeys, onTreeLoad = props.onTreeLoad, treeDefaultExpandAll = props.treeDefaultExpandAll, treeExpandedKeys = props.treeExpandedKeys, treeDefaultExpandedKeys = props.treeDefaultExpandedKeys, onTreeExpand = props.onTreeExpand, treeExpandAction = props.treeExpandAction, virtual = props.virtual, _props$listHeight = props.listHeight, listHeight = _props$listHeight === void 0 ? 200 : _props$listHeight, _props$listItemHeight = props.listItemHeight, listItemHeight = _props$listItemHeight === void 0 ? 20 : _props$listItemHeight, _props$listItemScroll = props.listItemScrollOffset, listItemScrollOffset = _props$listItemScroll === void 0 ? 0 : _props$listItemScroll, onDropdownVisibleChange = props.onDropdownVisibleChange, _props$dropdownMatchS = props.dropdownMatchSelectWidth, dropdownMatchSelectWidth = _props$dropdownMatchS === void 0 ? true : _props$dropdownMatchS, treeLine = props.treeLine, treeIcon = props.treeIcon, showTreeIcon = props.showTreeIcon, switcherIcon = props.switcherIcon, treeMotion = props.treeMotion, treeTitleRender = props.treeTitleRender, onPopupScroll = props.onPopupScroll, restProps = _objectWithoutProperties(props, _excluded); var mergedId = useId(id); var treeConduction = treeCheckable && !treeCheckStrictly; var mergedCheckable = treeCheckable || treeCheckStrictly; var mergedLabelInValue = treeCheckStrictly || labelInValue; var mergedMultiple = mergedCheckable || multiple; var _useMergedState = useMergedState(defaultValue, { value: value }), _useMergedState2 = _slicedToArray(_useMergedState, 2), internalValue = _useMergedState2[0], setInternalValue = _useMergedState2[1]; // `multiple` && `!treeCheckable` should be show all var mergedShowCheckedStrategy = React.useMemo(function () { if (!treeCheckable) { return SHOW_ALL; } return showCheckedStrategy || SHOW_CHILD; }, [showCheckedStrategy, treeCheckable]); // ========================== Warning =========================== if (process.env.NODE_ENV !== 'production') { warningProps(props); } // ========================= FieldNames ========================= var mergedFieldNames = React.useMemo(function () { return fillFieldNames(fieldNames); }, /* eslint-disable react-hooks/exhaustive-deps */ [JSON.stringify(fieldNames)] /* eslint-enable react-hooks/exhaustive-deps */); // =========================== Search =========================== var _useMergedState3 = useMergedState('', { value: searchValue !== undefined ? searchValue : inputValue, postState: function postState(search) { return search || ''; } }), _useMergedState4 = _slicedToArray(_useMergedState3, 2), mergedSearchValue = _useMergedState4[0], setSearchValue = _useMergedState4[1]; var onInternalSearch = function onInternalSearch(searchText) { setSearchValue(searchText); onSearch === null || onSearch === void 0 || onSearch(searchText); }; // ============================ Data ============================ // `useTreeData` only do convert of `children` or `simpleMode`. // Else will return origin `treeData` for perf consideration. // Do not do anything to loop the data. var mergedTreeData = useTreeData(treeData, children, treeDataSimpleMode); var _useDataEntities = useDataEntities(mergedTreeData, mergedFieldNames), keyEntities = _useDataEntities.keyEntities, valueEntities = _useDataEntities.valueEntities; /** Get `missingRawValues` which not exist in the tree yet */ var splitRawValues = React.useCallback(function (newRawValues) { var missingRawValues = []; var existRawValues = []; // Keep missing value in the cache newRawValues.forEach(function (val) { if (valueEntities.has(val)) { existRawValues.push(val); } else { missingRawValues.push(val); } }); return { missingRawValues: missingRawValues, existRawValues: existRawValues }; }, [valueEntities]); // Filtered Tree var filteredTreeData = useFilterTreeData(mergedTreeData, mergedSearchValue, { fieldNames: mergedFieldNames, treeNodeFilterProp: treeNodeFilterProp, filterTreeNode: filterTreeNode }); // =========================== Label ============================ var getLabel = React.useCallback(function (item) { if (item) { if (treeNodeLabelProp) { return item[treeNodeLabelProp]; } // Loop from fieldNames var titleList = mergedFieldNames._title; for (var i = 0; i < titleList.length; i += 1) { var title = item[titleList[i]]; if (title !== undefined) { return title; } } } }, [mergedFieldNames, treeNodeLabelProp]); // ========================= Wrap Value ========================= var toLabeledValues = React.useCallback(function (draftValues) { var values = toArray(draftValues); return values.map(function (val) { if (isRawValue(val)) { return { value: val }; } return val; }); }, []); var convert2LabelValues = React.useCallback(function (draftValues) { var values = toLabeledValues(draftValues); return values.map(function (item) { var rawLabel = item.label; var rawValue = item.value, rawHalfChecked = item.halfChecked; var rawDisabled; var entity = valueEntities.get(rawValue); // Fill missing label & status if (entity) { var _rawLabel; rawLabel = treeTitleRender ? treeTitleRender(entity.node) : (_rawLabel = rawLabel) !== null && _rawLabel !== void 0 ? _rawLabel : getLabel(entity.node); rawDisabled = entity.node.disabled; } else if (rawLabel === undefined) { // We try to find in current `labelInValue` value var labelInValueItem = toLabeledValues(internalValue).find(function (labeledItem) { return labeledItem.value === rawValue; }); rawLabel = labelInValueItem.label; } return { label: rawLabel, value: rawValue, halfChecked: rawHalfChecked, disabled: rawDisabled }; }); }, [valueEntities, getLabel, toLabeledValues, internalValue]); // =========================== Values =========================== var rawMixedLabeledValues = React.useMemo(function () { return toLabeledValues(internalValue); }, [toLabeledValues, internalValue]); // Split value into full check and half check var _React$useMemo = React.useMemo(function () { var fullCheckValues = []; var halfCheckValues = []; rawMixedLabeledValues.forEach(function (item) { if (item.halfChecked) { halfCheckValues.push(item); } else { fullCheckValues.push(item); } }); return [fullCheckValues, halfCheckValues]; }, [rawMixedLabeledValues]), _React$useMemo2 = _slicedToArray(_React$useMemo, 2), rawLabeledValues = _React$useMemo2[0], rawHalfLabeledValues = _React$useMemo2[1]; // const [mergedValues] = useCache(rawLabeledValues); var rawValues = React.useMemo(function () { return rawLabeledValues.map(function (item) { return item.value; }); }, [rawLabeledValues]); // Convert value to key. Will fill missed keys for conduct check. var _useCheckedKeys = useCheckedKeys(rawLabeledValues, rawHalfLabeledValues, treeConduction, keyEntities), _useCheckedKeys2 = _slicedToArray(_useCheckedKeys, 2), rawCheckedValues = _useCheckedKeys2[0], rawHalfCheckedValues = _useCheckedKeys2[1]; // Convert rawCheckedKeys to check strategy related values var displayValues = React.useMemo(function () { // Collect keys which need to show var displayKeys = formatStrategyValues(rawCheckedValues, mergedShowCheckedStrategy, keyEntities, mergedFieldNames); // Convert to value and filled with label var values = displayKeys.map(function (key) { var _keyEntities$key$node, _keyEntities$key; return (_keyEntities$key$node = (_keyEntities$key = keyEntities[key]) === null || _keyEntities$key === void 0 || (_keyEntities$key = _keyEntities$key.node) === null || _keyEntities$key === void 0 ? void 0 : _keyEntities$key[mergedFieldNames.value]) !== null && _keyEntities$key$node !== void 0 ? _keyEntities$key$node : key; }); // Back fill with origin label var labeledValues = values.map(function (val) { var targetItem = rawLabeledValues.find(function (item) { return item.value === val; }); var label; // Ensure that when labelInValue is true, if label is undefined, it remains undefined. if (labelInValue && targetItem.label !== undefined) { label = targetItem.label; } else if (!labelInValue && treeTitleRender) { label = treeTitleRender(targetItem); } return { value: val, label: label }; }); var rawDisplayValues = convert2LabelValues(labeledValues); var firstVal = rawDisplayValues[0]; if (!mergedMultiple && firstVal && isNil(firstVal.value) && isNil(firstVal.label)) { return []; } return rawDisplayValues.map(function (item) { var _item$label; return _objectSpread(_objectSpread({}, item), {}, { label: (_item$label = item.label) !== null && _item$label !== void 0 ? _item$label : item.value }); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [mergedFieldNames, mergedMultiple, rawCheckedValues, rawLabeledValues, convert2LabelValues, mergedShowCheckedStrategy, keyEntities]); var _useCache = useCache(displayValues), _useCache2 = _slicedToArray(_useCache, 1), cachedDisplayValues = _useCache2[0]; // =========================== Change =========================== var triggerChange = useRefFunc(function (newRawValues, extra, source) { var labeledValues = convert2LabelValues(newRawValues); setInternalValue(labeledValues); // Clean up if needed if (autoClearSearchValue) { setSearchValue(''); } // Generate rest parameters is costly, so only do it when necessary if (onChange) { var eventValues = newRawValues; if (treeConduction) { var formattedKeyList = formatStrategyValues(newRawValues, mergedShowCheckedStrategy, keyEntities, mergedFieldNames); eventValues = formattedKeyList.map(function (key) { var entity = valueEntities.get(key); return entity ? entity.node[mergedFieldNames.value] : key; }); } var _ref = extra || { triggerValue: undefined, selected: undefined }, triggerValue = _ref.triggerValue, selected = _ref.selected; var returnRawValues = eventValues; // We need fill half check back if (treeCheckStrictly) { var halfValues = rawHalfLabeledValues.filter(function (item) { return !eventValues.includes(item.value); }); returnRawValues = [].concat(_toConsumableArray(returnRawValues), _toConsumableArray(halfValues)); } var returnLabeledValues = convert2LabelValues(returnRawValues); var additionalInfo = { // [Legacy] Always return as array contains label & value preValue: rawLabeledValues, triggerValue: triggerValue }; // [Legacy] Fill legacy data if user query. // This is expansive that we only fill when user query // https://github.com/react-component/tree-select/blob/fe33eb7c27830c9ac70cd1fdb1ebbe7bc679c16a/src/Select.jsx var showPosition = true; if (treeCheckStrictly || source === 'selection' && !selected) { showPosition = false; } fillAdditionalInfo(additionalInfo, triggerValue, newRawValues, mergedTreeData, showPosition, mergedFieldNames); if (mergedCheckable) { additionalInfo.checked = selected; } else { additionalInfo.selected = selected; } var returnValues = mergedLabelInValue ? returnLabeledValues : returnLabeledValues.map(function (item) { return item.value; }); onChange(mergedMultiple ? returnValues : returnValues[0], mergedLabelInValue ? null : returnLabeledValues.map(function (item) { return item.label; }), additionalInfo); } }); // ========================== Options =========================== /** Trigger by option list */ var onOptionSelect = React.useCallback(function (selectedKey, _ref2) { var _node$mergedFieldName; var selected = _ref2.selected, source = _ref2.source; var entity = keyEntities[selectedKey]; var node = entity === null || entity === void 0 ? void 0 : entity.node; var selectedValue = (_node$mergedFieldName = node === null || node === void 0 ? void 0 : node[mergedFieldNames.value]) !== null && _node$mergedFieldName !== void 0 ? _node$mergedFieldName : selectedKey; // Never be falsy but keep it safe if (!mergedMultiple) { // Single mode always set value triggerChange([selectedValue], { selected: true, triggerValue: selectedValue }, 'option'); } else { var newRawValues = selected ? [].concat(_toConsumableArray(rawValues), [selectedValue]) : rawCheckedValues.filter(function (v) { return v !== selectedValue; }); // Add keys if tree conduction if (treeConduction) { // Should keep missing values var _splitRawValues = splitRawValues(newRawValues), missingRawValues = _splitRawValues.missingRawValues, existRawValues = _splitRawValues.existRawValues; var keyList = existRawValues.map(function (val) { return valueEntities.get(val).key; }); // Conduction by selected or not var checkedKeys; if (selected) { var _conductCheck = conductCheck(keyList, true, keyEntities); checkedKeys = _conductCheck.checkedKeys; } else { var _conductCheck2 = conductCheck(keyList, { checked: false, halfCheckedKeys: rawHalfCheckedValues }, keyEntities); checkedKeys = _conductCheck2.checkedKeys; } // Fill back of keys newRawValues = [].concat(_toConsumableArray(missingRawValues), _toConsumableArray(checkedKeys.map(function (key) { return keyEntities[key].node[mergedFieldNames.value]; }))); } triggerChange(newRawValues, { selected: selected, triggerValue: selectedValue }, source || 'option'); } // Trigger select event if (selected || !mergedMultiple) { onSelect === null || onSelect === void 0 || onSelect(selectedValue, fillLegacyProps(node)); } else { onDeselect === null || onDeselect === void 0 || onDeselect(selectedValue, fillLegacyProps(node)); } }, [splitRawValues, valueEntities, keyEntities, mergedFieldNames, mergedMultiple, rawValues, triggerChange, treeConduction, onSelect, onDeselect, rawCheckedValues, rawHalfCheckedValues]); // ========================== Dropdown ========================== var onInternalDropdownVisibleChange = React.useCallback(function (open) { if (onDropdownVisibleChange) { var legacyParam = {}; Object.defineProperty(legacyParam, 'documentClickClose', { get: function get() { warning(false, 'Second param of `onDropdownVisibleChange` has been removed.'); return false; } }); onDropdownVisibleChange(open, legacyParam); } }, [onDropdownVisibleChange]); // ====================== Display Change ======================== var onDisplayValuesChange = useRefFunc(function (newValues, info) { var newRawValues = newValues.map(function (item) { return item.value; }); if (info.type === 'clear') { triggerChange(newRawValues, {}, 'selection'); return; } // TreeSelect only have multiple mode which means display change only has remove if (info.values.length) { onOptionSelect(info.values[0].value, { selected: false, source: 'selection' }); } }); // ========================== Context =========================== var treeSelectContext = React.useMemo(function () { return { virtual: virtual, dropdownMatchSelectWidth: dropdownMatchSelectWidth, listHeight: listHeight, listItemHeight: listItemHeight, listItemScrollOffset: listItemScrollOffset, treeData: filteredTreeData, fieldNames: mergedFieldNames, onSelect: onOptionSelect, treeExpandAction: treeExpandAction, treeTitleRender: treeTitleRender, onPopupScroll: onPopupScroll }; }, [virtual, dropdownMatchSelectWidth, listHeight, listItemHeight, listItemScrollOffset, filteredTreeData, mergedFieldNames, onOptionSelect, treeExpandAction, treeTitleRender, onPopupScroll]); // ======================= Legacy Context ======================= var legacyContext = React.useMemo(function () { return { checkable: mergedCheckable, loadData: loadData, treeLoadedKeys: treeLoadedKeys, onTreeLoad: onTreeLoad, checkedKeys: rawCheckedValues, halfCheckedKeys: rawHalfCheckedValues, treeDefaultExpandAll: treeDefaultExpandAll, treeExpandedKeys: treeExpandedKeys, treeDefaultExpandedKeys: treeDefaultExpandedKeys, onTreeExpand: onTreeExpand, treeIcon: treeIcon, treeMotion: treeMotion, showTreeIcon: showTreeIcon, switcherIcon: switcherIcon, treeLine: treeLine, treeNodeFilterProp: treeNodeFilterProp, keyEntities: keyEntities }; }, [mergedCheckable, loadData, treeLoadedKeys, onTreeLoad, rawCheckedValues, rawHalfCheckedValues, treeDefaultExpandAll, treeExpandedKeys, treeDefaultExpandedKeys, onTreeExpand, treeIcon, treeMotion, showTreeIcon, switcherIcon, treeLine, treeNodeFilterProp, keyEntities]); // =========================== Render =========================== return /*#__PURE__*/React.createElement(TreeSelectContext.Provider, { value: treeSelectContext }, /*#__PURE__*/React.createElement(LegacyContext.Provider, { value: legacyContext }, /*#__PURE__*/React.createElement(BaseSelect, _extends({ ref: ref }, restProps, { // >>> MISC id: mergedId, prefixCls: prefixCls, mode: mergedMultiple ? 'multiple' : undefined // >>> Display Value , displayValues: cachedDisplayValues, onDisplayValuesChange: onDisplayValuesChange // >>> Search , searchValue: mergedSearchValue, onSearch: onInternalSearch // >>> Options , OptionList: OptionList, emptyOptions: !mergedTreeData.length, onDropdownVisibleChange: onInternalDropdownVisibleChange, dropdownMatchSelectWidth: dropdownMatchSelectWidth })))); }); // Assign name for Debug if (process.env.NODE_ENV !== 'production') { TreeSelect.displayName = 'TreeSelect'; } var GenericTreeSelect = TreeSelect; GenericTreeSelect.TreeNode = TreeNode; GenericTreeSelect.SHOW_ALL = SHOW_ALL; GenericTreeSelect.SHOW_PARENT = SHOW_PARENT; GenericTreeSelect.SHOW_CHILD = SHOW_CHILD; export default GenericTreeSelect;