This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
@@ -0,0 +1,24 @@
import * as React from 'react';
import type { DataEntity, IconType } from '@rc-component/tree/lib/interface';
import type { LegacyDataNode, SafeKey, Key } from './interface';
interface LegacyContextProps {
checkable: boolean | React.ReactNode;
checkedKeys: Key[];
halfCheckedKeys: Key[];
treeExpandedKeys: Key[];
treeDefaultExpandedKeys: Key[];
onTreeExpand: (keys: Key[]) => void;
treeDefaultExpandAll: boolean;
treeIcon: IconType;
showTreeIcon: boolean;
switcherIcon: IconType;
treeLine: boolean;
treeNodeFilterProp: string;
treeLoadedKeys: Key[];
treeMotion: any;
loadData: (treeNode: LegacyDataNode) => Promise<unknown>;
onTreeLoad: (loadedKeys: Key[]) => void;
keyEntities: Record<SafeKey, DataEntity<any>>;
}
declare const LegacySelectContext: React.Context<LegacyContextProps>;
export default LegacySelectContext;
@@ -0,0 +1,3 @@
import * as React from 'react';
const LegacySelectContext = /*#__PURE__*/React.createContext(null);
export default LegacySelectContext;
@@ -0,0 +1,8 @@
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import type { ScrollTo } from '@rc-component/tree/lib/interface';
import * as React from 'react';
type ReviseRefOptionListProps = Omit<RefOptionListProps, 'scrollTo'> & {
scrollTo: ScrollTo;
};
declare const RefOptionList: React.ForwardRefExoticComponent<React.RefAttributes<ReviseRefOptionListProps>>;
export default RefOptionList;
+355
View File
@@ -0,0 +1,355 @@
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); }
import { useBaseProps } from '@rc-component/select';
import Tree from '@rc-component/tree';
import { UnstableContext } from '@rc-component/tree';
import KeyCode from "@rc-component/util/es/KeyCode";
import useMemo from "@rc-component/util/es/hooks/useMemo";
import * as React from 'react';
import LegacyContext from "./LegacyContext";
import TreeSelectContext from "./TreeSelectContext";
import { getAllKeys, isCheckDisabled } from "./utils/valueUtil";
import { useEvent } from '@rc-component/util';
const HIDDEN_STYLE = {
width: 0,
height: 0,
display: 'flex',
overflow: 'hidden',
opacity: 0,
border: 0,
padding: 0,
margin: 0
};
const OptionList = (_, ref) => {
const {
prefixCls,
multiple,
searchValue,
toggleOpen,
open,
notFoundContent
} = useBaseProps();
const {
virtual,
listHeight,
listItemHeight,
listItemScrollOffset,
treeData,
fieldNames,
onSelect,
popupMatchSelectWidth,
treeExpandAction,
treeTitleRender,
onPopupScroll,
leftMaxCount,
leafCountOnly,
valueEntities,
classNames: treeClassNames,
styles
} = React.useContext(TreeSelectContext);
const {
checkable,
checkedKeys,
halfCheckedKeys,
treeExpandedKeys,
treeDefaultExpandAll,
treeDefaultExpandedKeys,
onTreeExpand,
treeIcon,
showTreeIcon,
switcherIcon,
treeLine,
treeNodeFilterProp,
loadData,
treeLoadedKeys,
treeMotion,
onTreeLoad,
keyEntities
} = React.useContext(LegacyContext);
const treeRef = React.useRef();
const memoTreeData = useMemo(() => treeData,
// eslint-disable-next-line react-hooks/exhaustive-deps
[open, treeData], (prev, next) => next[0] && prev[1] !== next[1]);
// ========================== Values ==========================
const mergedCheckedKeys = React.useMemo(() => {
if (!checkable) {
return null;
}
return {
checked: checkedKeys,
halfChecked: halfCheckedKeys
};
}, [checkable, checkedKeys, halfCheckedKeys]);
// ========================== Scroll ==========================
React.useEffect(() => {
// Single mode should scroll to current key
if (open && !multiple && checkedKeys.length) {
treeRef.current?.scrollTo({
key: checkedKeys[0]
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
// ========================== Events ==========================
const onListMouseDown = event => {
event.preventDefault();
};
const onInternalSelect = (__, info) => {
const {
node
} = info;
if (checkable && isCheckDisabled(node)) {
return;
}
onSelect(node.key, {
selected: !checkedKeys.includes(node.key)
});
if (!multiple) {
toggleOpen(false);
}
};
// =========================== Keys ===========================
const [expandedKeys, setExpandedKeys] = React.useState(treeDefaultExpandedKeys);
const [searchExpandedKeys, setSearchExpandedKeys] = React.useState(null);
const mergedExpandedKeys = React.useMemo(() => {
if (treeExpandedKeys) {
return [...treeExpandedKeys];
}
return searchValue ? searchExpandedKeys : expandedKeys;
}, [expandedKeys, searchExpandedKeys, treeExpandedKeys, searchValue]);
const onInternalExpand = keys => {
setExpandedKeys(keys);
setSearchExpandedKeys(keys);
if (onTreeExpand) {
onTreeExpand(keys);
}
};
// ========================== Search ==========================
const lowerSearchValue = String(searchValue).toLowerCase();
const filterTreeNode = treeNode => {
if (!lowerSearchValue) {
return false;
}
return String(treeNode[treeNodeFilterProp]).toLowerCase().includes(lowerSearchValue);
};
React.useEffect(() => {
if (searchValue) {
setSearchExpandedKeys(getAllKeys(treeData, fieldNames));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchValue]);
// ========================= Disabled =========================
// Cache disabled states in React state to ensure re-render when cache updates
const [disabledCache, setDisabledCache] = React.useState(() => new Map());
React.useEffect(() => {
if (leftMaxCount) {
setDisabledCache(new Map());
}
}, [leftMaxCount]);
function getDisabledWithCache(node) {
const value = node[fieldNames.value];
if (!disabledCache.has(value)) {
const entity = valueEntities.get(value);
const isLeaf = (entity.children || []).length === 0;
if (!isLeaf) {
const checkableChildren = entity.children.filter(childTreeNode => !childTreeNode.node.disabled && !childTreeNode.node.disableCheckbox && !checkedKeys.includes(childTreeNode.node[fieldNames.value]));
const checkableChildrenCount = checkableChildren.length;
disabledCache.set(value, checkableChildrenCount > leftMaxCount);
} else {
disabledCache.set(value, false);
}
}
return disabledCache.get(value);
}
const nodeDisabled = useEvent(node => {
const nodeValue = node[fieldNames.value];
if (checkedKeys.includes(nodeValue)) {
return false;
}
if (leftMaxCount === null) {
return false;
}
if (leftMaxCount <= 0) {
return true;
}
// This is a low performance calculation
if (leafCountOnly && leftMaxCount) {
return getDisabledWithCache(node);
}
return false;
});
// ========================== Get First Selectable Node ==========================
const getFirstMatchingNode = nodes => {
for (const node of nodes) {
if (node.disabled || node.selectable === false) {
continue;
}
if (searchValue) {
if (filterTreeNode(node)) {
return node;
}
} else {
return node;
}
if (node[fieldNames.children]) {
const matchInChildren = getFirstMatchingNode(node[fieldNames.children]);
if (matchInChildren) {
return matchInChildren;
}
}
}
return null;
};
// ========================== Active ==========================
const [activeKey, setActiveKey] = React.useState(null);
const activeEntity = keyEntities[activeKey];
React.useEffect(() => {
if (!open) {
return;
}
let nextActiveKey = null;
const getFirstNode = () => {
const firstNode = getFirstMatchingNode(memoTreeData);
return firstNode ? firstNode[fieldNames.value] : null;
};
// single mode active first checked node
if (!multiple && checkedKeys.length && !searchValue) {
nextActiveKey = checkedKeys[0];
} else {
nextActiveKey = getFirstNode();
}
setActiveKey(nextActiveKey);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, searchValue]);
// ========================= Keyboard =========================
React.useImperativeHandle(ref, () => ({
scrollTo: treeRef.current?.scrollTo,
onKeyDown: event => {
const {
which
} = event;
switch (which) {
// >>> Arrow keys
case KeyCode.UP:
case KeyCode.DOWN:
case KeyCode.LEFT:
case KeyCode.RIGHT:
treeRef.current?.onKeyDown(event);
break;
// >>> Select item
case KeyCode.ENTER:
{
if (activeEntity) {
const isNodeDisabled = nodeDisabled(activeEntity.node);
const {
selectable,
value,
disabled
} = activeEntity?.node || {};
if (selectable !== false && !disabled && !isNodeDisabled) {
onInternalSelect(null, {
node: {
key: activeKey
},
selected: !checkedKeys.includes(value)
});
}
}
break;
}
// >>> Close
case KeyCode.ESC:
{
toggleOpen(false);
}
}
},
onKeyUp: () => {}
}));
const hasLoadDataFn = useMemo(() => searchValue ? false : true,
// eslint-disable-next-line react-hooks/exhaustive-deps
[searchValue, treeExpandedKeys || expandedKeys], ([preSearchValue], [nextSearchValue, nextExcludeSearchExpandedKeys]) => preSearchValue !== nextSearchValue && !!(nextSearchValue || nextExcludeSearchExpandedKeys));
const syncLoadData = hasLoadDataFn ? loadData : null;
// ========================== Render ==========================
if (memoTreeData.length === 0) {
return /*#__PURE__*/React.createElement("div", {
role: "listbox",
className: `${prefixCls}-empty`,
onMouseDown: onListMouseDown
}, notFoundContent);
}
const treeProps = {
fieldNames
};
if (treeLoadedKeys) {
treeProps.loadedKeys = treeLoadedKeys;
}
if (mergedExpandedKeys) {
treeProps.expandedKeys = mergedExpandedKeys;
}
return /*#__PURE__*/React.createElement("div", {
onMouseDown: onListMouseDown
}, activeEntity && open && /*#__PURE__*/React.createElement("span", {
style: HIDDEN_STYLE,
"aria-live": "assertive"
}, activeEntity.node.value), /*#__PURE__*/React.createElement(UnstableContext.Provider, {
value: {
nodeDisabled
}
}, /*#__PURE__*/React.createElement(Tree, _extends({
classNames: treeClassNames?.popup,
styles: styles?.popup,
ref: treeRef,
focusable: false,
prefixCls: `${prefixCls}-tree`,
treeData: memoTreeData,
height: listHeight,
itemHeight: listItemHeight,
itemScrollOffset: listItemScrollOffset,
virtual: virtual !== false && popupMatchSelectWidth !== false,
multiple: multiple,
icon: treeIcon,
showIcon: showTreeIcon,
switcherIcon: switcherIcon,
showLine: treeLine,
loadData: syncLoadData,
motion: treeMotion,
activeKey: activeKey
// We handle keys by out instead tree self
,
checkable: checkable,
checkStrictly: true,
checkedKeys: mergedCheckedKeys,
selectedKeys: !checkable ? checkedKeys : [],
defaultExpandAll: treeDefaultExpandAll,
titleRender: treeTitleRender
}, treeProps, {
// Proxy event out
onActiveChange: setActiveKey,
onSelect: onInternalSelect,
onCheck: onInternalSelect,
onExpand: onInternalExpand,
onLoad: onTreeLoad,
filterTreeNode: filterTreeNode,
expandAction: treeExpandAction,
onScroll: onPopupScroll
}))));
};
const RefOptionList = /*#__PURE__*/React.forwardRef(OptionList);
if (process.env.NODE_ENV !== 'production') {
RefOptionList.displayName = 'OptionList';
}
export default RefOptionList;
@@ -0,0 +1,9 @@
import type * as React from 'react';
import type { DataNode, Key } from './interface';
export interface TreeNodeProps extends Omit<DataNode, 'children'> {
value: Key;
children?: React.ReactNode;
}
/** This is a placeholder, not real render in dom */
declare const TreeNode: React.FC<TreeNodeProps>;
export default TreeNode;
@@ -0,0 +1,5 @@
/* istanbul ignore file */
/** This is a placeholder, not real render in dom */
const TreeNode = () => null;
export default TreeNode;
@@ -0,0 +1,85 @@
import type { BaseSelectPropsWithoutPrivate, BaseSelectRef } from '@rc-component/select';
import type { BaseSelectSemanticName } from '@rc-component/select/lib/BaseSelect';
import type { IconType } from '@rc-component/tree/lib/interface';
import type { ExpandAction } from '@rc-component/tree/lib/Tree';
import * as React from 'react';
import TreeNode from './TreeNode';
import type { CheckedStrategy } from './utils/strategyUtil';
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
import type { SafeKey, DataNode, SimpleModeConfig, ChangeEventExtra, FieldNames, LegacyDataNode } from './interface';
export type SemanticName = BaseSelectSemanticName;
export type PopupSemantic = 'item' | 'itemTitle';
export interface SearchConfig {
searchValue?: string;
onSearch?: (value: string) => void;
autoClearSearchValue?: boolean;
filterTreeNode?: boolean | ((inputValue: string, treeNode: DataNode) => boolean);
treeNodeFilterProp?: string;
}
export interface TreeSelectProps<ValueType = any, OptionType extends DataNode = DataNode> extends Omit<BaseSelectPropsWithoutPrivate, 'mode' | 'classNames' | 'styles' | 'showSearch'> {
prefixCls?: string;
id?: string;
children?: React.ReactNode;
styles?: Partial<Record<SemanticName, React.CSSProperties>> & {
popup?: Partial<Record<PopupSemantic, React.CSSProperties>>;
};
classNames?: Partial<Record<SemanticName, string>> & {
popup?: Partial<Record<PopupSemantic, string>>;
};
value?: ValueType;
defaultValue?: ValueType;
onChange?: (value: ValueType, labelList: React.ReactNode[], extra: ChangeEventExtra) => void;
showSearch?: boolean | SearchConfig;
/** @deprecated Use `showSearch.searchValue` instead */
searchValue?: string;
/** @deprecated Use `showSearch.searchValue` instead */
inputValue?: string;
/** @deprecated Use `showSearch.onSearch` instead */
onSearch?: (value: string) => void;
/** @deprecated Use `showSearch.autoClearSearchValue` instead */
autoClearSearchValue?: boolean;
/** @deprecated Use `showSearch.filterTreeNode` instead */
filterTreeNode?: boolean | ((inputValue: string, treeNode: DataNode) => boolean);
/** @deprecated Use `showSearch.treeNodeFilterProp` instead */
treeNodeFilterProp?: string;
onSelect?: (value: ValueType, option: OptionType) => void;
onDeselect?: (value: ValueType, option: OptionType) => void;
showCheckedStrategy?: CheckedStrategy;
treeNodeLabelProp?: string;
fieldNames?: FieldNames;
multiple?: boolean;
treeCheckable?: boolean | React.ReactNode;
treeCheckStrictly?: boolean;
labelInValue?: boolean;
maxCount?: number;
treeData?: OptionType[];
treeDataSimpleMode?: boolean | SimpleModeConfig;
loadData?: (dataNode: LegacyDataNode) => Promise<unknown>;
treeLoadedKeys?: SafeKey[];
onTreeLoad?: (loadedKeys: SafeKey[]) => void;
treeDefaultExpandAll?: boolean;
treeExpandedKeys?: SafeKey[];
treeDefaultExpandedKeys?: SafeKey[];
onTreeExpand?: (expandedKeys: SafeKey[]) => void;
treeExpandAction?: ExpandAction;
virtual?: boolean;
listHeight?: number;
listItemHeight?: number;
listItemScrollOffset?: number;
onPopupVisibleChange?: (open: boolean) => void;
treeTitleRender?: (node: OptionType) => React.ReactNode;
treeLine?: boolean;
treeIcon?: IconType;
showTreeIcon?: boolean;
switcherIcon?: IconType;
treeMotion?: any;
}
declare const GenericTreeSelect: (<ValueType = any, OptionType extends DataNode = DataNode>(props: React.PropsWithChildren<TreeSelectProps<ValueType, OptionType>> & {
ref?: React.Ref<BaseSelectRef>;
}) => React.ReactElement) & {
TreeNode: typeof TreeNode;
SHOW_ALL: typeof SHOW_ALL;
SHOW_PARENT: typeof SHOW_PARENT;
SHOW_CHILD: typeof SHOW_CHILD;
};
export default GenericTreeSelect;
+530
View File
@@ -0,0 +1,530 @@
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); }
import { BaseSelect } from '@rc-component/select';
import useId from "@rc-component/util/es/hooks/useId";
import { conductCheck } from "@rc-component/tree/es/utils/conductUtil";
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
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";
import useSearchConfig from "./hooks/useSearchConfig";
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 = useId(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] = useSearchConfig(showSearch, searchProps);
const {
searchValue,
onSearch,
autoClearSearchValue = true,
filterTreeNode,
treeNodeFilterProp = 'value'
} = searchConfig;
const [internalValue, setInternalValue] = useControlledState(defaultValue, value);
// `multiple` && `!treeCheckable` should be show all
const mergedShowCheckedStrategy = React.useMemo(() => {
if (!treeCheckable) {
return SHOW_ALL;
}
return showCheckedStrategy || SHOW_CHILD;
}, [showCheckedStrategy, treeCheckable]);
// ========================== Warning ===========================
if (process.env.NODE_ENV !== 'production') {
warningProps(props);
}
// ========================= FieldNames =========================
const mergedFieldNames = React.useMemo(() => fillFieldNames(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
[JSON.stringify(fieldNames)]
/* eslint-enable react-hooks/exhaustive-deps */);
// =========================== Search ===========================
const [internalSearchValue, setSearchValue] = useControlledState('', 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 = useTreeData(treeData, children, treeDataSimpleMode);
const {
keyEntities,
valueEntities
} = useDataEntities(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 = useFilterTreeData(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 = 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] = useCheckedKeys(rawLabeledValues, rawHalfLabeledValues, treeConduction, keyEntities);
// Convert rawCheckedKeys to check strategy related values
const displayValues = React.useMemo(() => {
// Collect keys which need to show
const displayKeys = 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 && isNil(firstVal.value) && 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] = useCache(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 = useRefFunc((newRawValues, extra, source) => {
const formattedKeyList = 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;
}
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
} = conductCheck(keyList, true, keyEntities));
} else {
({
checkedKeys
} = 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, fillLegacyProps(node));
} else {
onDeselect?.(selectedValue, 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 = useRefFunc((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.Provider, {
value: treeSelectContext
}, /*#__PURE__*/React.createElement(LegacyContext.Provider, {
value: legacyContext
}, /*#__PURE__*/React.createElement(BaseSelect, _extends({
ref: ref
}, restProps, {
classNames: treeSelectClassNames,
styles: styles
// >>> 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,
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;
GenericTreeSelect.SHOW_ALL = SHOW_ALL;
GenericTreeSelect.SHOW_PARENT = SHOW_PARENT;
GenericTreeSelect.SHOW_CHILD = SHOW_CHILD;
export default GenericTreeSelect;
@@ -0,0 +1,28 @@
import * as React from 'react';
import type { ExpandAction } from '@rc-component/tree/lib/Tree';
import type { DataNode, FieldNames, Key } from './interface';
import type useDataEntities from './hooks/useDataEntities';
import { TreeSelectProps } from './TreeSelect';
export interface TreeSelectContextProps {
virtual?: boolean;
popupMatchSelectWidth?: boolean | number;
listHeight: number;
listItemHeight: number;
listItemScrollOffset?: number;
treeData: DataNode[];
fieldNames: FieldNames;
onSelect: (value: Key, info: {
selected: boolean;
}) => void;
treeExpandAction?: ExpandAction;
treeTitleRender?: (node: any) => React.ReactNode;
onPopupScroll?: React.UIEventHandler<HTMLDivElement>;
leftMaxCount: number | null;
/** When `true`, only take leaf node as count, or take all as count with `maxCount` limitation */
leafCountOnly: boolean;
valueEntities: ReturnType<typeof useDataEntities>['valueEntities'];
classNames: TreeSelectProps['classNames'];
styles: TreeSelectProps['styles'];
}
declare const TreeSelectContext: React.Context<TreeSelectContextProps>;
export default TreeSelectContext;
@@ -0,0 +1,3 @@
import * as React from 'react';
const TreeSelectContext = /*#__PURE__*/React.createContext(null);
export default TreeSelectContext;
@@ -0,0 +1,7 @@
import type { LabeledValueType } from '../interface';
/**
* This function will try to call requestIdleCallback if available to save performance.
* No need `getLabel` here since already fetch on `rawLabeledValue`.
*/
declare const _default: (values: LabeledValueType[]) => [LabeledValueType[]];
export default _default;
@@ -0,0 +1,32 @@
import * as React from 'react';
/**
* This function will try to call requestIdleCallback if available to save performance.
* No need `getLabel` here since already fetch on `rawLabeledValue`.
*/
export default (values => {
const cacheRef = React.useRef({
valueLabels: new Map()
});
return React.useMemo(() => {
const {
valueLabels
} = cacheRef.current;
const valueLabelsCache = new Map();
const filledValues = values.map(item => {
const {
value,
label
} = item;
const mergedLabel = label ?? valueLabels.get(value);
// Save in cache
valueLabelsCache.set(value, mergedLabel);
return {
...item,
label: mergedLabel
};
});
cacheRef.current.valueLabels = valueLabelsCache;
return [filledValues];
}, [values]);
});
@@ -0,0 +1,5 @@
import * as React from 'react';
import type { DataEntity } from '@rc-component/tree/lib/interface';
import type { LabeledValueType, SafeKey } from '../interface';
declare const useCheckedKeys: (rawLabeledValues: LabeledValueType[], rawHalfCheckedValues: LabeledValueType[], treeConduction: boolean, keyEntities: Record<SafeKey, DataEntity>) => React.Key[][];
export default useCheckedKeys;
@@ -0,0 +1,21 @@
import * as React from 'react';
import { conductCheck } from "@rc-component/tree/es/utils/conductUtil";
const useCheckedKeys = (rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities) => {
return React.useMemo(() => {
const extractValues = values => values.map(({
value
}) => value);
const checkedKeys = extractValues(rawLabeledValues);
const halfCheckedKeys = extractValues(rawHalfCheckedValues);
const missingValues = checkedKeys.filter(key => !keyEntities[key]);
let finalCheckedKeys = checkedKeys;
let finalHalfCheckedKeys = halfCheckedKeys;
if (treeConduction) {
const conductResult = conductCheck(checkedKeys, true, keyEntities);
finalCheckedKeys = conductResult.checkedKeys;
finalHalfCheckedKeys = conductResult.halfCheckedKeys;
}
return [Array.from(new Set([...missingValues, ...finalCheckedKeys])), finalHalfCheckedKeys];
}, [rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities]);
};
export default useCheckedKeys;
@@ -0,0 +1,7 @@
import type { DataEntity } from '@rc-component/tree/lib/interface';
import type { SafeKey, FieldNames } from '../interface';
declare const _default: (treeData: any, fieldNames: FieldNames) => {
valueEntities: Map<SafeKey, DataEntity>;
keyEntities: Record<string, DataEntity>;
};
export default _default;
@@ -0,0 +1,26 @@
import * as React from 'react';
import { convertDataToEntities } from "@rc-component/tree/es/utils/treeUtil";
import warning from "@rc-component/util/es/warning";
import { isNil } from "../utils/valueUtil";
export default ((treeData, fieldNames) => React.useMemo(() => {
const collection = convertDataToEntities(treeData, {
fieldNames,
initWrapper: wrapper => ({
...wrapper,
valueEntities: new Map()
}),
processEntity: (entity, wrapper) => {
const val = entity.node[fieldNames.value];
// Check if exist same value
if (process.env.NODE_ENV !== 'production') {
const key = entity.node.key;
warning(!isNil(val), 'TreeNode `value` is invalidate: undefined');
warning(!wrapper.valueEntities.has(val), `Same \`value\` exist in the tree: ${val}`);
warning(!key || String(key) === String(val), `\`key\` or \`value\` with TreeNode must be the same or you can remove one of them. key: ${key}, value: ${val}.`);
}
wrapper.valueEntities.set(val, entity);
}
});
return collection;
}, [treeData, fieldNames]));
@@ -0,0 +1,8 @@
import type { TreeSelectProps } from '../TreeSelect';
import type { DataNode, FieldNames } from '../interface';
declare const useFilterTreeData: (treeData: DataNode[], searchValue: string, options: {
fieldNames: FieldNames;
treeNodeFilterProp: string;
filterTreeNode: TreeSelectProps['filterTreeNode'];
}) => DataNode[];
export default useFilterTreeData;
@@ -0,0 +1,33 @@
import * as React from 'react';
import { fillLegacyProps } from "../utils/legacyUtil";
const useFilterTreeData = (treeData, searchValue, options) => {
const {
fieldNames,
treeNodeFilterProp,
filterTreeNode
} = options;
const {
children: fieldChildren
} = fieldNames;
return React.useMemo(() => {
if (!searchValue || filterTreeNode === false) {
return treeData;
}
const filterOptionFunc = typeof filterTreeNode === 'function' ? filterTreeNode : (_, dataNode) => String(dataNode[treeNodeFilterProp]).toUpperCase().includes(searchValue.toUpperCase());
const filterTreeNodes = (nodes, keepAll = false) => nodes.reduce((filtered, node) => {
const children = node[fieldChildren];
const isMatch = keepAll || filterOptionFunc(searchValue, fillLegacyProps(node));
const filteredChildren = filterTreeNodes(children || [], isMatch);
if (isMatch || filteredChildren.length) {
filtered.push({
...node,
isLeaf: undefined,
[fieldChildren]: filteredChildren
});
}
return filtered;
}, []);
return filterTreeNodes(treeData);
}, [treeData, searchValue, fieldChildren, treeNodeFilterProp, filterTreeNode]);
};
export default useFilterTreeData;
@@ -0,0 +1,5 @@
/**
* Same as `React.useCallback` but always return a memoized function
* but redirect to real function.
*/
export default function useRefFunc<T extends (...args: any[]) => any>(callback: T): T;
@@ -0,0 +1,14 @@
import * as React from 'react';
/**
* Same as `React.useCallback` but always return a memoized function
* but redirect to real function.
*/
export default function useRefFunc(callback) {
const funcRef = React.useRef();
funcRef.current = callback;
const cacheFn = React.useCallback((...args) => {
return funcRef.current(...args);
}, []);
return cacheFn;
}
@@ -0,0 +1,4 @@
import type { SearchConfig } from "../TreeSelect";
export default function useSearchConfig(showSearch: boolean | SearchConfig, props: SearchConfig & {
inputValue: string;
}): [boolean, SearchConfig];
@@ -0,0 +1,25 @@
import * as React from 'react';
// Convert `showSearch` to unique config
export default function useSearchConfig(showSearch, props) {
const {
searchValue,
inputValue,
onSearch,
autoClearSearchValue,
filterTreeNode,
treeNodeFilterProp
} = props;
return React.useMemo(() => {
const isObject = typeof showSearch === 'object';
const searchConfig = {
searchValue: searchValue ?? inputValue,
onSearch,
autoClearSearchValue,
filterTreeNode,
treeNodeFilterProp,
...(isObject ? showSearch : {})
};
return [isObject ? true : showSearch, searchConfig];
}, [showSearch, searchValue, inputValue, onSearch, autoClearSearchValue, filterTreeNode, treeNodeFilterProp]);
}
@@ -0,0 +1,7 @@
import * as React from 'react';
import type { DataNode, SimpleModeConfig } from '../interface';
/**
* 将 `treeData` 或 `children` 转换为格式化的 `treeData`。
* 如果 `treeData` 或 `children` 没有变化,则不会重新计算。
*/
export default function useTreeData(treeData: DataNode[], children: React.ReactNode, simpleMode: boolean | SimpleModeConfig): DataNode[];
@@ -0,0 +1,52 @@
import * as React from 'react';
import { convertChildrenToData } from "../utils/legacyUtil";
function buildTreeStructure(nodes, config) {
const {
id,
pId,
rootPId
} = config;
const nodeMap = new Map();
const rootNodes = [];
nodes.forEach(node => {
const nodeKey = node[id];
const clonedNode = {
...node,
key: node.key || nodeKey
};
nodeMap.set(nodeKey, clonedNode);
});
nodeMap.forEach(node => {
const parentKey = node[pId];
const parent = nodeMap.get(parentKey);
if (parent) {
parent.children = parent.children || [];
parent.children.push(node);
} else if (parentKey === rootPId || rootPId === null) {
rootNodes.push(node);
}
});
return rootNodes;
}
/**
* 将 `treeData` 或 `children` 转换为格式化的 `treeData`。
* 如果 `treeData` 或 `children` 没有变化,则不会重新计算。
*/
export default function useTreeData(treeData, children, simpleMode) {
return React.useMemo(() => {
if (treeData) {
if (simpleMode) {
const config = {
id: 'id',
pId: 'pId',
rootPId: null,
...(typeof simpleMode === 'object' ? simpleMode : {})
};
return buildTreeStructure(treeData, config);
}
return treeData;
}
return convertChildrenToData(children);
}, [children, simpleMode, treeData]);
}
+7
View File
@@ -0,0 +1,7 @@
import TreeSelect from './TreeSelect';
import type { TreeSelectProps } from './TreeSelect';
import TreeNode from './TreeNode';
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT };
export type { TreeSelectProps };
export default TreeSelect;
+5
View File
@@ -0,0 +1,5 @@
import TreeSelect from "./TreeSelect";
import TreeNode from "./TreeNode";
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from "./utils/strategyUtil";
export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT };
export default TreeSelect;
@@ -0,0 +1,57 @@
import type * as React from 'react';
import type { SafeKey, Key, DataNode as TreeDataNode } from '@rc-component/tree/lib/interface';
export type { SafeKey, Key };
export interface DataNode extends Record<string, any>, Omit<TreeDataNode, 'key' | 'children'> {
key?: Key;
value?: SafeKey;
children?: DataNode[];
}
export type SelectSource = 'option' | 'selection' | 'input' | 'clear';
export interface LabeledValueType {
key?: Key;
value?: SafeKey;
label?: React.ReactNode;
/** Only works on `treeCheckStrictly` */
halfChecked?: boolean;
}
export type DefaultValueType = SafeKey | LabeledValueType | (SafeKey | LabeledValueType)[];
export interface LegacyDataNode extends DataNode {
props: any;
}
export interface FlattenDataNode {
data: DataNode;
key: Key;
value: SafeKey;
level: number;
parent?: FlattenDataNode;
}
export interface SimpleModeConfig {
id?: SafeKey;
pId?: SafeKey;
rootPId?: SafeKey;
}
/** @deprecated This is only used for legacy compatible. Not works on new code. */
export interface LegacyCheckedNode {
pos: string;
node: React.ReactElement;
children?: LegacyCheckedNode[];
}
export interface ChangeEventExtra {
/** @deprecated Please save prev value by control logic instead */
preValue: LabeledValueType[];
triggerValue: SafeKey;
/** @deprecated Use `onSelect` or `onDeselect` instead. */
selected?: boolean;
/** @deprecated Use `onSelect` or `onDeselect` instead. */
checked?: boolean;
/** @deprecated This prop not work as react node anymore. */
triggerNode: React.ReactElement;
/** @deprecated This prop not work as react node anymore. */
allCheckedNodes: LegacyCheckedNode[];
}
export interface FieldNames {
value?: string;
label?: string;
children?: string;
_title?: string[];
}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,5 @@
import * as React from 'react';
import type { DataNode, ChangeEventExtra, SafeKey, FieldNames } from '../interface';
export declare function convertChildrenToData(nodes: React.ReactNode): DataNode[];
export declare function fillLegacyProps(dataNode: DataNode): DataNode;
export declare function fillAdditionalInfo(extra: ChangeEventExtra, triggerValue: SafeKey, checkedValues: SafeKey[], treeData: DataNode[], showPosition: boolean, fieldNames: FieldNames): void;
@@ -0,0 +1,120 @@
import * as React from 'react';
import toArray from "@rc-component/util/es/Children/toArray";
import warning from "@rc-component/util/es/warning";
import TreeNode from "../TreeNode";
export function convertChildrenToData(nodes) {
return toArray(nodes).map(node => {
if (! /*#__PURE__*/React.isValidElement(node) || !node.type) {
return null;
}
const {
key,
props: {
children,
value,
...restProps
}
} = node;
const data = {
key,
value,
...restProps
};
const childData = convertChildrenToData(children);
if (childData.length) {
data.children = childData;
}
return data;
}).filter(data => data);
}
export function fillLegacyProps(dataNode) {
if (!dataNode) {
return dataNode;
}
const cloneNode = {
...dataNode
};
if (!('props' in cloneNode)) {
Object.defineProperty(cloneNode, 'props', {
get() {
warning(false, 'New `rc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.');
return cloneNode;
}
});
}
return cloneNode;
}
export function fillAdditionalInfo(extra, triggerValue, checkedValues, treeData, showPosition, fieldNames) {
let triggerNode = null;
let nodeList = null;
function generateMap() {
function dig(list, level = '0', parentIncluded = false) {
return list.map((option, index) => {
const pos = `${level}-${index}`;
const value = option[fieldNames.value];
const included = checkedValues.includes(value);
const children = dig(option[fieldNames.children] || [], pos, included);
const node = /*#__PURE__*/React.createElement(TreeNode, option, children.map(child => child.node));
// Link with trigger node
if (triggerValue === value) {
triggerNode = node;
}
if (included) {
const checkedNode = {
pos,
node,
children
};
if (!parentIncluded) {
nodeList.push(checkedNode);
}
return checkedNode;
}
return null;
}).filter(node => node);
}
if (!nodeList) {
nodeList = [];
dig(treeData);
// Sort to keep the checked node length
nodeList.sort(({
node: {
props: {
value: val1
}
}
}, {
node: {
props: {
value: val2
}
}
}) => {
const index1 = checkedValues.indexOf(val1);
const index2 = checkedValues.indexOf(val2);
return index1 - index2;
});
}
}
Object.defineProperty(extra, 'triggerNode', {
get() {
warning(false, '`triggerNode` is deprecated. Please consider decoupling data with node.');
generateMap();
return triggerNode;
}
});
Object.defineProperty(extra, 'allCheckedNodes', {
get() {
warning(false, '`allCheckedNodes` is deprecated. Please consider decoupling data with node.');
generateMap();
if (showPosition) {
return nodeList;
}
return nodeList.map(({
node
}) => node);
}
});
}
@@ -0,0 +1,7 @@
import type { DataEntity } from '@rc-component/tree/lib/interface';
import type { SafeKey, FieldNames } from '../interface';
export declare const SHOW_ALL = "SHOW_ALL";
export declare const SHOW_PARENT = "SHOW_PARENT";
export declare const SHOW_CHILD = "SHOW_CHILD";
export type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD;
export declare function formatStrategyValues(values: SafeKey[], strategy: CheckedStrategy, keyEntities: Record<SafeKey, DataEntity>, fieldNames: FieldNames): SafeKey[];
@@ -0,0 +1,25 @@
import { isCheckDisabled } from "./valueUtil";
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_PARENT = 'SHOW_PARENT';
export const SHOW_CHILD = 'SHOW_CHILD';
export function formatStrategyValues(values, strategy, keyEntities, fieldNames) {
const valueSet = new Set(values);
if (strategy === SHOW_CHILD) {
return values.filter(key => {
const entity = keyEntities[key];
return !entity || !entity.children || !entity.children.some(({
node
}) => valueSet.has(node[fieldNames.value])) || !entity.children.every(({
node
}) => isCheckDisabled(node) || valueSet.has(node[fieldNames.value]));
});
}
if (strategy === SHOW_PARENT) {
return values.filter(key => {
const entity = keyEntities[key];
const parent = entity ? entity.parent : null;
return !parent || isCheckDisabled(parent.node) || !valueSet.has(parent.key);
});
}
return values;
}
@@ -0,0 +1,11 @@
import type { DataNode, FieldNames, SafeKey } from '../interface';
export declare const toArray: <T>(value: T | T[]) => T[];
export declare const fillFieldNames: (fieldNames?: FieldNames) => {
_title: string[];
value: string;
key: string;
children: string;
};
export declare const isCheckDisabled: (node: DataNode) => boolean;
export declare const getAllKeys: (treeData: DataNode[], fieldNames: FieldNames) => SafeKey[];
export declare const isNil: (val: any) => boolean;
@@ -0,0 +1,30 @@
export const toArray = value => Array.isArray(value) ? value : value !== undefined ? [value] : [];
export const fillFieldNames = fieldNames => {
const {
label,
value,
children
} = fieldNames || {};
return {
_title: label ? [label] : ['title', 'label'],
value: value || 'value',
key: value || 'value',
children: children || 'children'
};
};
export const isCheckDisabled = node => !node || node.disabled || node.disableCheckbox || node.checkable === false;
export const getAllKeys = (treeData, fieldNames) => {
const keys = [];
const dig = list => {
list.forEach(item => {
const children = item[fieldNames.children];
if (children) {
keys.push(item[fieldNames.value]);
dig(children);
}
});
};
dig(treeData);
return keys;
};
export const isNil = val => val === null || val === undefined;
@@ -0,0 +1,5 @@
import type { TreeSelectProps } from '../TreeSelect';
declare function warningProps(props: TreeSelectProps & {
searchPlaceholder?: string;
}): void;
export default warningProps;
@@ -0,0 +1,30 @@
import warning from "@rc-component/util/es/warning";
import { toArray } from "./valueUtil";
function warningProps(props) {
const {
searchPlaceholder,
treeCheckStrictly,
treeCheckable,
labelInValue,
value,
multiple,
showCheckedStrategy,
maxCount
} = props;
warning(!searchPlaceholder, '`searchPlaceholder` has been removed.');
if (treeCheckStrictly && labelInValue === false) {
warning(false, '`treeCheckStrictly` will force set `labelInValue` to `true`.');
}
if (labelInValue || treeCheckStrictly) {
warning(toArray(value).every(val => val && typeof val === 'object' && 'value' in val), 'Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.');
}
if (treeCheckStrictly || multiple || treeCheckable) {
warning(!value || Array.isArray(value), '`value` should be an array when `TreeSelect` is checkable or multiple.');
} else {
warning(!Array.isArray(value), '`value` should not be array when `TreeSelect` is single mode.');
}
if (maxCount && (showCheckedStrategy === 'SHOW_ALL' && !treeCheckStrictly || showCheckedStrategy === 'SHOW_PARENT')) {
warning(false, '`maxCount` not work with `showCheckedStrategy=SHOW_ALL` (when `treeCheckStrictly=false`) or `showCheckedStrategy=SHOW_PARENT`.');
}
}
export default warningProps;