UNPKG

@rc-component/tree-select

Version:
547 lines (507 loc) 20.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _select = require("@rc-component/select"); var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId")); var _conductUtil = require("@rc-component/tree/lib/utils/conductUtil"); var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState")); var React = _interopRequireWildcard(require("react")); var _useCache = _interopRequireDefault(require("./hooks/useCache")); var _useCheckedKeys = _interopRequireDefault(require("./hooks/useCheckedKeys")); var _useDataEntities = _interopRequireDefault(require("./hooks/useDataEntities")); var _useFilterTreeData = _interopRequireDefault(require("./hooks/useFilterTreeData")); var _useRefFunc = _interopRequireDefault(require("./hooks/useRefFunc")); var _useTreeData = _interopRequireDefault(require("./hooks/useTreeData")); var _LegacyContext = _interopRequireDefault(require("./LegacyContext")); var _OptionList = _interopRequireDefault(require("./OptionList")); var _TreeNode = _interopRequireDefault(require("./TreeNode")); var _TreeSelectContext = _interopRequireDefault(require("./TreeSelectContext")); var _legacyUtil = require("./utils/legacyUtil"); var _strategyUtil = require("./utils/strategyUtil"); var _valueUtil = require("./utils/valueUtil"); var _warningPropsUtil = _interopRequireDefault(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); } function isRawValue(value) { return !value || typeof value !== 'object'; } const TreeSelect = /*#__PURE__*/React.forwardRef((props, ref) => { const { id, prefixCls = 'rc-tree-select', // Value value, defaultValue, onChange, onSelect, onDeselect, // Search showSearch, searchValue: legacySearchValue, inputValue: legacyinputValue, onSearch: legacyOnSearch, autoClearSearchValue: legacyAutoClearSearchValue, filterTreeNode: legacyFilterTreeNode, treeNodeFilterProp: legacytreeNodeFilterProp, // Selector showCheckedStrategy, treeNodeLabelProp, // Mode multiple, treeCheckable, treeCheckStrictly, labelInValue, maxCount, // FieldNames fieldNames, // Data treeDataSimpleMode, treeData, children, loadData, treeLoadedKeys, onTreeLoad, // Expanded treeDefaultExpandAll, treeExpandedKeys, treeDefaultExpandedKeys, onTreeExpand, treeExpandAction, // Options virtual, listHeight = 200, listItemHeight = 20, listItemScrollOffset = 0, onPopupVisibleChange, popupMatchSelectWidth = true, // Tree treeLine, treeIcon, showTreeIcon, switcherIcon, treeMotion, treeTitleRender, onPopupScroll, classNames: treeSelectClassNames, styles, ...restProps } = props; const mergedId = (0, _useId.default)(id); const treeConduction = treeCheckable && !treeCheckStrictly; const mergedCheckable = treeCheckable || treeCheckStrictly; const mergedLabelInValue = treeCheckStrictly || labelInValue; const mergedMultiple = mergedCheckable || multiple; const searchProps = { searchValue: legacySearchValue, inputValue: legacyinputValue, onSearch: legacyOnSearch, autoClearSearchValue: legacyAutoClearSearchValue, filterTreeNode: legacyFilterTreeNode, treeNodeFilterProp: legacytreeNodeFilterProp }; const [mergedShowSearch, searchConfig] = (0, _useSearchConfig.default)(showSearch, searchProps); const { searchValue, onSearch, autoClearSearchValue = true, filterTreeNode, treeNodeFilterProp = 'value' } = searchConfig; const [internalValue, setInternalValue] = (0, _useControlledState.default)(defaultValue, value); // `multiple` && `!treeCheckable` should be show all const mergedShowCheckedStrategy = React.useMemo(() => { if (!treeCheckable) { return _strategyUtil.SHOW_ALL; } return showCheckedStrategy || _strategyUtil.SHOW_CHILD; }, [showCheckedStrategy, treeCheckable]); // ========================== Warning =========================== if (process.env.NODE_ENV !== 'production') { (0, _warningPropsUtil.default)(props); } // ========================= FieldNames ========================= const mergedFieldNames = React.useMemo(() => (0, _valueUtil.fillFieldNames)(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */ [JSON.stringify(fieldNames)] /* eslint-enable react-hooks/exhaustive-deps */); // =========================== Search =========================== const [internalSearchValue, setSearchValue] = (0, _useControlledState.default)('', searchValue); const mergedSearchValue = internalSearchValue || ''; const onInternalSearch = searchText => { setSearchValue(searchText); 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. const mergedTreeData = (0, _useTreeData.default)(treeData, children, treeDataSimpleMode); const { keyEntities, valueEntities } = (0, _useDataEntities.default)(mergedTreeData, mergedFieldNames); /** Get `missingRawValues` which not exist in the tree yet */ const splitRawValues = React.useCallback(newRawValues => { const missingRawValues = []; const existRawValues = []; // Keep missing value in the cache newRawValues.forEach(val => { if (valueEntities.has(val)) { existRawValues.push(val); } else { missingRawValues.push(val); } }); return { missingRawValues, existRawValues }; }, [valueEntities]); // Filtered Tree const filteredTreeData = (0, _useFilterTreeData.default)(mergedTreeData, mergedSearchValue, { fieldNames: mergedFieldNames, treeNodeFilterProp, filterTreeNode }); // =========================== Label ============================ const getLabel = React.useCallback(item => { if (item) { if (treeNodeLabelProp) { return item[treeNodeLabelProp]; } // Loop from fieldNames const { _title: titleList } = mergedFieldNames; for (let i = 0; i < titleList.length; i += 1) { const title = item[titleList[i]]; if (title !== undefined) { return title; } } } }, [mergedFieldNames, treeNodeLabelProp]); // ========================= Wrap Value ========================= const toLabeledValues = React.useCallback(draftValues => { const values = (0, _valueUtil.toArray)(draftValues); return values.map(val => { if (isRawValue(val)) { return { value: val }; } return val; }); }, []); const convert2LabelValues = React.useCallback(draftValues => { const values = toLabeledValues(draftValues); return values.map(item => { let { label: rawLabel } = item; const { value: rawValue, halfChecked: rawHalfChecked } = item; let rawDisabled; const entity = valueEntities.get(rawValue); // Fill missing label & status if (entity) { rawLabel = treeTitleRender ? treeTitleRender(entity.node) : rawLabel ?? getLabel(entity.node); rawDisabled = entity.node.disabled; } else if (rawLabel === undefined) { // We try to find in current `labelInValue` value const labelInValueItem = toLabeledValues(internalValue).find(labeledItem => labeledItem.value === rawValue); rawLabel = labelInValueItem.label; } return { label: rawLabel, value: rawValue, halfChecked: rawHalfChecked, disabled: rawDisabled }; }); }, [valueEntities, getLabel, toLabeledValues, internalValue]); // =========================== Values =========================== const rawMixedLabeledValues = React.useMemo(() => toLabeledValues(internalValue === null ? [] : internalValue), [toLabeledValues, internalValue]); // Split value into full check and half check const [rawLabeledValues, rawHalfLabeledValues] = React.useMemo(() => { const fullCheckValues = []; const halfCheckValues = []; rawMixedLabeledValues.forEach(item => { if (item.halfChecked) { halfCheckValues.push(item); } else { fullCheckValues.push(item); } }); return [fullCheckValues, halfCheckValues]; }, [rawMixedLabeledValues]); // const [mergedValues] = useCache(rawLabeledValues); const rawValues = React.useMemo(() => rawLabeledValues.map(item => item.value), [rawLabeledValues]); // Convert value to key. Will fill missed keys for conduct check. const [rawCheckedValues, rawHalfCheckedValues] = (0, _useCheckedKeys.default)(rawLabeledValues, rawHalfLabeledValues, treeConduction, keyEntities); // Convert rawCheckedKeys to check strategy related values const displayValues = React.useMemo(() => { // Collect keys which need to show const displayKeys = (0, _strategyUtil.formatStrategyValues)(rawCheckedValues, mergedShowCheckedStrategy, keyEntities, mergedFieldNames); // Convert to value and filled with label const values = displayKeys.map(key => keyEntities[key]?.node?.[mergedFieldNames.value] ?? key); // Back fill with origin label const labeledValues = values.map(val => { const targetItem = rawLabeledValues.find(item => item.value === val); const label = labelInValue ? targetItem?.label : treeTitleRender?.(targetItem); return { value: val, label }; }); const rawDisplayValues = convert2LabelValues(labeledValues); const firstVal = rawDisplayValues[0]; if (!mergedMultiple && firstVal && (0, _valueUtil.isNil)(firstVal.value) && (0, _valueUtil.isNil)(firstVal.label)) { return []; } return rawDisplayValues.map(item => ({ ...item, label: item.label ?? item.value })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [mergedFieldNames, mergedMultiple, rawCheckedValues, rawLabeledValues, convert2LabelValues, mergedShowCheckedStrategy, keyEntities]); const [cachedDisplayValues] = (0, _useCache.default)(displayValues); // ========================== MaxCount ========================== const mergedMaxCount = React.useMemo(() => { if (mergedMultiple && (mergedShowCheckedStrategy === 'SHOW_CHILD' || treeCheckStrictly || !treeCheckable)) { return maxCount; } return null; }, [maxCount, mergedMultiple, treeCheckStrictly, mergedShowCheckedStrategy, treeCheckable]); // =========================== Change =========================== const triggerChange = (0, _useRefFunc.default)((newRawValues, extra, source) => { const formattedKeyList = (0, _strategyUtil.formatStrategyValues)(newRawValues, mergedShowCheckedStrategy, keyEntities, mergedFieldNames); // Not allow pass with `maxCount` if (mergedMaxCount && formattedKeyList.length > mergedMaxCount) { return; } const 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) { let eventValues = newRawValues; if (treeConduction) { eventValues = formattedKeyList.map(key => { const entity = valueEntities.get(key); return entity ? entity.node[mergedFieldNames.value] : key; }); } const { triggerValue, selected } = extra || { triggerValue: undefined, selected: undefined }; let returnRawValues = eventValues; // We need fill half check back if (treeCheckStrictly) { const halfValues = rawHalfLabeledValues.filter(item => !eventValues.includes(item.value)); returnRawValues = [...returnRawValues, ...halfValues]; } const returnLabeledValues = convert2LabelValues(returnRawValues); const additionalInfo = { // [Legacy] Always return as array contains label & value preValue: rawLabeledValues, 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 let showPosition = true; if (treeCheckStrictly || source === 'selection' && !selected) { showPosition = false; } (0, _legacyUtil.fillAdditionalInfo)(additionalInfo, triggerValue, newRawValues, mergedTreeData, showPosition, mergedFieldNames); if (mergedCheckable) { additionalInfo.checked = selected; } else { additionalInfo.selected = selected; } const returnValues = mergedLabelInValue ? returnLabeledValues : returnLabeledValues.map(item => item.value); onChange(mergedMultiple ? returnValues : returnValues[0], mergedLabelInValue ? null : returnLabeledValues.map(item => item.label), additionalInfo); } }); // ========================== Options =========================== /** Trigger by option list */ const onOptionSelect = React.useCallback((selectedKey, { selected, source }) => { const entity = keyEntities[selectedKey]; const node = entity?.node; const selectedValue = node?.[mergedFieldNames.value] ?? selectedKey; // Never be falsy but keep it safe if (!mergedMultiple) { // Single mode always set value triggerChange([selectedValue], { selected: true, triggerValue: selectedValue }, 'option'); } else { let newRawValues = selected ? [...rawValues, selectedValue] : rawCheckedValues.filter(v => v !== selectedValue); // Add keys if tree conduction if (treeConduction) { // Should keep missing values const { missingRawValues, existRawValues } = splitRawValues(newRawValues); const keyList = existRawValues.map(val => valueEntities.get(val).key); // Conduction by selected or not let checkedKeys; if (selected) { ({ checkedKeys } = (0, _conductUtil.conductCheck)(keyList, true, keyEntities)); } else { ({ checkedKeys } = (0, _conductUtil.conductCheck)(keyList, { checked: false, halfCheckedKeys: rawHalfCheckedValues }, keyEntities)); } // Fill back of keys newRawValues = [...missingRawValues, ...checkedKeys.map(key => keyEntities[key].node[mergedFieldNames.value])]; } triggerChange(newRawValues, { selected, triggerValue: selectedValue }, source || 'option'); } // Trigger select event if (selected || !mergedMultiple) { onSelect?.(selectedValue, (0, _legacyUtil.fillLegacyProps)(node)); } else { onDeselect?.(selectedValue, (0, _legacyUtil.fillLegacyProps)(node)); } }, [splitRawValues, valueEntities, keyEntities, mergedFieldNames, mergedMultiple, rawValues, triggerChange, treeConduction, onSelect, onDeselect, rawCheckedValues, rawHalfCheckedValues, maxCount]); // ========================== Dropdown ========================== const onInternalPopupVisibleChange = React.useCallback(open => { if (onPopupVisibleChange) { onPopupVisibleChange(open); } }, [onPopupVisibleChange]); // ====================== Display Change ======================== const onDisplayValuesChange = (0, _useRefFunc.default)((newValues, info) => { const newRawValues = newValues.map(item => 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 =========================== const treeSelectContext = React.useMemo(() => { return { virtual, popupMatchSelectWidth, listHeight, listItemHeight, listItemScrollOffset, treeData: filteredTreeData, fieldNames: mergedFieldNames, onSelect: onOptionSelect, treeExpandAction, treeTitleRender, onPopupScroll, leftMaxCount: maxCount === undefined ? null : maxCount - cachedDisplayValues.length, leafCountOnly: mergedShowCheckedStrategy === 'SHOW_CHILD' && !treeCheckStrictly && !!treeCheckable, valueEntities, classNames: treeSelectClassNames, styles }; }, [virtual, popupMatchSelectWidth, listHeight, listItemHeight, listItemScrollOffset, filteredTreeData, mergedFieldNames, onOptionSelect, treeExpandAction, treeTitleRender, onPopupScroll, maxCount, cachedDisplayValues.length, mergedShowCheckedStrategy, treeCheckStrictly, treeCheckable, valueEntities, treeSelectClassNames, styles]); // ======================= Legacy Context ======================= const legacyContext = React.useMemo(() => ({ checkable: mergedCheckable, loadData, treeLoadedKeys, onTreeLoad, checkedKeys: rawCheckedValues, halfCheckedKeys: rawHalfCheckedValues, treeDefaultExpandAll, treeExpandedKeys, treeDefaultExpandedKeys, onTreeExpand, treeIcon, treeMotion, showTreeIcon, switcherIcon, treeLine, treeNodeFilterProp, keyEntities }), [mergedCheckable, loadData, treeLoadedKeys, onTreeLoad, rawCheckedValues, rawHalfCheckedValues, treeDefaultExpandAll, treeExpandedKeys, treeDefaultExpandedKeys, onTreeExpand, treeIcon, treeMotion, showTreeIcon, switcherIcon, treeLine, treeNodeFilterProp, keyEntities]); // =========================== Render =========================== return /*#__PURE__*/React.createElement(_TreeSelectContext.default.Provider, { value: treeSelectContext }, /*#__PURE__*/React.createElement(_LegacyContext.default.Provider, { value: legacyContext }, /*#__PURE__*/React.createElement(_select.BaseSelect, _extends({ ref: ref }, restProps, { classNames: { prefix: treeSelectClassNames?.prefix, suffix: treeSelectClassNames?.suffix, input: treeSelectClassNames?.input }, styles: { prefix: styles?.prefix, suffix: styles?.suffix, input: styles?.input } // >>> MISC , id: mergedId, prefixCls: prefixCls, mode: mergedMultiple ? 'multiple' : undefined // >>> Display Value , displayValues: cachedDisplayValues, onDisplayValuesChange: onDisplayValuesChange // >>> Search , autoClearSearchValue: autoClearSearchValue, showSearch: mergedShowSearch, searchValue: mergedSearchValue, onSearch: onInternalSearch // >>> Options , OptionList: _OptionList.default, emptyOptions: !mergedTreeData.length, onPopupVisibleChange: onInternalPopupVisibleChange, popupMatchSelectWidth: popupMatchSelectWidth })))); }); // Assign name for Debug if (process.env.NODE_ENV !== 'production') { TreeSelect.displayName = 'TreeSelect'; } const GenericTreeSelect = TreeSelect; GenericTreeSelect.TreeNode = _TreeNode.default; GenericTreeSelect.SHOW_ALL = _strategyUtil.SHOW_ALL; GenericTreeSelect.SHOW_PARENT = _strategyUtil.SHOW_PARENT; GenericTreeSelect.SHOW_CHILD = _strategyUtil.SHOW_CHILD; var _default = exports.default = GenericTreeSelect;