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
+22
View File
@@ -0,0 +1,22 @@
MIT LICENSE
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+140
View File
@@ -0,0 +1,140 @@
# @rc-component/tree-select
React TreeSelect Component.
<!-- prettier-ignore -->
[![NPM version][npm-image]][npm-url]
[![npm download][download-image]][download-url]
[![build status][github-actions-image]][github-actions-url]
[![Codecov][codecov-image]][codecov-url]
[![bundle size][bundlephobia-image]][bundlephobia-url]
[![dumi][dumi-image]][dumi-url]
[npm-image]: http://img.shields.io/npm/v/@rc-component/tree-select.svg?style=flat-square
[npm-url]: http://npmjs.org/package/@rc-component/tree-select
[travis-image]: https://img.shields.io/travis/react-component/tree-select/master?style=flat-square
[travis-url]: https://travis-ci.com/react-component/tree-select
[github-actions-image]: https://github.com/react-component/tree-select/actions/workflows/react-component-ci.yml/badge.svg
[github-actions-url]: https://github.com/react-component/tree-select/actions/workflows/react-component-ci.yml
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tree-select/master.svg?style=flat-square
[codecov-url]: https://app.codecov.io/gh/react-component/tree-select
[david-url]: https://david-dm.org/react-component/tree-select
[david-image]: https://david-dm.org/react-component/tree-select/status.svg?style=flat-square
[david-dev-url]: https://david-dm.org/react-component/tree-select?type=dev
[david-dev-image]: https://david-dm.org/react-component/tree-select/dev-status.svg?style=flat-square
[download-image]: https://img.shields.io/npm/dm/@rc-component/tree-select.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/tree-select
[bundlephobia-url]: https://bundlephobia.com/package/@rc-component/tree-select
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/tree-select
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
## Screenshots
<img src="https://os.alipayobjects.com/rmsportal/HUhyhmpWyiGKnZF.png" width="288"/>
## Development
```
npm install
npm start
```
## Example
http://localhost:8000/examples/
online example: https://tree-select-react-component.vercel.app/
## install
[![@rc-component/tree-select](https://nodei.co/npm/@rc-component/tree-select.png)](https://npmjs.org/package/@rc-component/tree-select)
## API
### TreeSelect props
| name | description | type | default |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- |
| className | additional css class of root dom node | String | '' |
| prefixCls | prefix class | String | '' |
| animation | dropdown animation name. only support slide-up now | String | '' |
| transitionName | dropdown css animation name | String | '' |
| choiceTransitionName | css animation name for selected items at multiple mode | String | '' |
| popupMatchSelectWidth | whether dropdown's with is same with select. Default set `min-width` same as input | bool | true |
| dropdownClassName | additional className applied to dropdown | String | - |
| dropdownStyle | additional style applied to dropdown | Object | {} |
| onPopupVisibleChange | control dropdown visible | function | `() => { return true; }` |
| notFoundContent | specify content to show when no result matches. | String | 'Not Found' |
| showSearch | whether show search input in single mode | bool | true |
| allowClear | whether allowClear | bool | false |
| maxTagTextLength | max tag text length to show | number | - |
| maxTagCount | max tag count to show | number | - |
| maxCount | Limit the maximum number of items that can be selected in multiple mode | number | - |
| maxTagPlaceholder | placeholder for omitted values | ReactNode/function(omittedValues) | - |
| multiple | whether multiple select (true when enable treeCheckable) | bool | false |
| disabled | whether disabled select | bool | false |
| searchValue | work with `onSearch` to make search value controlled. | string | '' |
| defaultValue | initial selected treeNode(s) | same as value type | - |
| value | current selected treeNode(s). | normal: String/Array<String>. labelInValue: {value:String,label:React.Node}/Array<{value,label}>. treeCheckStrictly(halfChecked default false): {value:String,label:React.Node, halfChecked}/Array<{value,label,halfChecked}>. | - |
| labelInValue | whether to embed label in value, see above value type | Bool | false |
| onChange | called when select treeNode or input value change | function(value, label(null), extra) | - |
| onSelect | called when select treeNode | function(value, node, extra) | - |
| onSearch | called when input changed | function | - |
| onTreeExpand | called when tree node expand | function(expandedKeys) | - |
| onPopupScroll | called when popup scroll | function(event) | - |
| showCheckedStrategy | `TreeSelect.SHOW_ALL`: show all checked treeNodes (Include parent treeNode). `TreeSelect.SHOW_PARENT`: show checked treeNodes (Just show parent treeNode). Default just show child. | enum{TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
| treeIcon | show tree icon | bool | false |
| treeLine | show tree line | bool | false |
| treeDefaultExpandAll | default expand all treeNode | bool | false |
| treeDefaultExpandedKeys | default expanded treeNode keys | Array<String> | - |
| treeExpandedKeys | set tree expanded keys | Array<String> | - |
| treeExpandAction | Tree open logic, optional: false \| `click` \| `doubleClick`, same as `expandAction` of `rc-tree` | string \| boolean | `click` |
| treeCheckable | whether tree show checkbox (select callback will not fire) | bool | false |
| treeCheckStrictly | check node precisely, parent and children nodes are not associated | bool | false |
| filterTreeNode | whether filter treeNodes by input value. default filter by treeNode's treeNodeFilterProp prop's value | bool/Function(inputValue:string, treeNode:TreeNode) | Function |
| treeNodeFilterProp | which prop value of treeNode will be used for filter if filterTreeNode return true | String | 'value' |
| treeNodeLabelProp | which prop value of treeNode will render as content of select | String | 'title' |
| treeData | treeNodes data Array, if set it then you need not to construct children TreeNode. (value should be unique across the whole array) | array<{value,label,children, [disabled,selectable]}> | [] |
| treeDataSimpleMode | enable simple mode of treeData.(treeData should be like this: [{id:1, pId:0, value:'1', label:"test1",...},...], `pId` is parent node's id) | bool/object{id:'id', pId:'pId', rootPId:null} | false |
| treeTitleRender | Custom render nodes | (nodeData: OptionType) => ReactNode |
| loadData | load data asynchronously | function(node) | - |
| getPopupContainer | container which popup select menu rendered into | function(trigger:Node):Node | function(){return document.body;} |
| autoClearSearchValue | auto clear search input value when multiple select is selected/deselected | boolean | true |
| suffixIcon | specify the select arrow icon | ReactNode \| (props: TreeProps) => ReactNode | - |
| clearIcon | specify the clear icon | ReactNode \| (props: TreeProps) => ReactNode | - |
| removeIcon | specify the remove icon | ReactNode \| (props: TreeProps) => ReactNode | - |
| switcherIcon | specify the switcher icon | ReactNode \| (props: TreeProps) => ReactNode | - |
| virtual | Disable virtual when `false` | false | - |
### TreeNode props
> note: you'd better to use `treeData` instead of using TreeNode.
| name | description | type | default |
| -------- | ------------------------------------------------------------------------- | -------------- | ------- |
| disabled | disable treeNode | bool | false |
| key | it's value must be unique across the tree's all TreeNode, you must set it | String | - |
| value | default as treeNodeFilterProp (be unique across the tree's all TreeNode) | String | '' |
| title | tree/subTree's title | String/element | '---' |
| isLeaf | whether it's leaf node | bool | false |
## note
1. Optimization tips(when there are large amounts of data, like more than 5000 nodes)
- Do not Expand all nodes.
- Recommend not exist many `TreeSelect` components in a page at the same time.
- Recommend not use `treeCheckable` mode, or use `treeCheckStrictly`.
2. In `treeCheckable` mode, It has the same effect when click `x`(node in Selection box) or uncheck in the treeNode(in dropdown panel), but the essence is not the same. So, even if both of them trigger `onChange` method, but the parameters (the third parameter) are different. (中文:在`treeCheckable`模式下,已选择节点上的`x`删除操作、和相应 treeNode 节点上 checkbox 的 uncheck 操作,最终效果相同,但本质不一样。前者跟弹出的 tree 组件可以“毫无关系”(例如 dropdown 没展开过,tree 也就没渲染好),而后者是 tree 组件上的节点 uncheck 事件。所以、即便两者都会触发`onChange`方法、但它们的参数(第三个参数)是不同的。)
## Test Case
http://localhost:8000/tests/runner.html?coverage
## Coverage
http://localhost:8000/node_modules/rc-server/node_modules/node-jscover/lib/front-end/jscoverage.html?w=http://localhost:8000/tests/runner.html?coverage
## License
@rc-component/tree-select is released under the MIT license.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,2 @@
@import "./select.less";
@import "./tree.less";
Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

@@ -0,0 +1,3 @@
@import '~@rc-component/select/assets/index';
@select-prefix: ~'rc-tree-select';
@@ -0,0 +1,3 @@
@import '~rc-tree/assets/index';
@treePrefixCls: ~'rc-tree-select-tree';
@@ -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;
@@ -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,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
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; }
const LegacySelectContext = /*#__PURE__*/React.createContext(null);
var _default = exports.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;
@@ -0,0 +1,363 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _select = require("@rc-component/select");
var _tree = _interopRequireWildcard(require("@rc-component/tree"));
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
var _useMemo = _interopRequireDefault(require("@rc-component/util/lib/hooks/useMemo"));
var React = _interopRequireWildcard(require("react"));
var _LegacyContext = _interopRequireDefault(require("./LegacyContext"));
var _TreeSelectContext = _interopRequireDefault(require("./TreeSelectContext"));
var _valueUtil = require("./utils/valueUtil");
var _util = require("@rc-component/util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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 _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); }
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
} = (0, _select.useBaseProps)();
const {
virtual,
listHeight,
listItemHeight,
listItemScrollOffset,
treeData,
fieldNames,
onSelect,
popupMatchSelectWidth,
treeExpandAction,
treeTitleRender,
onPopupScroll,
leftMaxCount,
leafCountOnly,
valueEntities,
classNames: treeClassNames,
styles
} = React.useContext(_TreeSelectContext.default);
const {
checkable,
checkedKeys,
halfCheckedKeys,
treeExpandedKeys,
treeDefaultExpandAll,
treeDefaultExpandedKeys,
onTreeExpand,
treeIcon,
showTreeIcon,
switcherIcon,
treeLine,
treeNodeFilterProp,
loadData,
treeLoadedKeys,
treeMotion,
onTreeLoad,
keyEntities
} = React.useContext(_LegacyContext.default);
const treeRef = React.useRef();
const memoTreeData = (0, _useMemo.default)(() => 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 && (0, _valueUtil.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((0, _valueUtil.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 = (0, _util.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.default.UP:
case _KeyCode.default.DOWN:
case _KeyCode.default.LEFT:
case _KeyCode.default.RIGHT:
treeRef.current?.onKeyDown(event);
break;
// >>> Select item
case _KeyCode.default.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.default.ESC:
{
toggleOpen(false);
}
}
},
onKeyUp: () => {}
}));
const hasLoadDataFn = (0, _useMemo.default)(() => 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(_tree.UnstableContext.Provider, {
value: {
nodeDisabled
}
}, /*#__PURE__*/React.createElement(_tree.default, _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';
}
var _default = exports.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;
+11
View File
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
/* istanbul ignore file */
/** This is a placeholder, not real render in dom */
const TreeNode = () => null;
var _default = exports.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;
@@ -0,0 +1,539 @@
"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: 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.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;
@@ -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,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
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; }
const TreeSelectContext = /*#__PURE__*/React.createContext(null);
var _default = exports.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,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
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; }
/**
* This function will try to call requestIdleCallback if available to save performance.
* No need `getLabel` here since already fetch on `rawLabeledValue`.
*/
var _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]);
};
exports.default = _default;
@@ -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,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _conductUtil = require("@rc-component/tree/lib/utils/conductUtil");
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; }
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 = (0, _conductUtil.conductCheck)(checkedKeys, true, keyEntities);
finalCheckedKeys = conductResult.checkedKeys;
finalHalfCheckedKeys = conductResult.halfCheckedKeys;
}
return [Array.from(new Set([...missingValues, ...finalCheckedKeys])), finalHalfCheckedKeys];
}, [rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities]);
};
var _default = exports.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,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _treeUtil = require("@rc-component/tree/lib/utils/treeUtil");
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _valueUtil = require("../utils/valueUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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; }
var _default = (treeData, fieldNames) => React.useMemo(() => {
const collection = (0, _treeUtil.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;
(0, _warning.default)(!(0, _valueUtil.isNil)(val), 'TreeNode `value` is invalidate: undefined');
(0, _warning.default)(!wrapper.valueEntities.has(val), `Same \`value\` exist in the tree: ${val}`);
(0, _warning.default)(!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]);
exports.default = _default;
@@ -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,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _legacyUtil = require("../utils/legacyUtil");
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; }
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, (0, _legacyUtil.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]);
};
var _default = exports.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,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useRefFunc;
var React = _interopRequireWildcard(require("react"));
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; }
/**
* Same as `React.useCallback` but always return a memoized function
* but redirect to real function.
*/
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,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useSearchConfig;
var React = _interopRequireWildcard(require("react"));
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; }
// Convert `showSearch` to unique config
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,60 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useTreeData;
var React = _interopRequireWildcard(require("react"));
var _legacyUtil = require("../utils/legacyUtil");
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 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` 没有变化,则不会重新计算。
*/
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 (0, _legacyUtil.convertChildrenToData)(children);
}, [children, simpleMode, treeData]);
}
@@ -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;
+35
View File
@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "SHOW_ALL", {
enumerable: true,
get: function () {
return _strategyUtil.SHOW_ALL;
}
});
Object.defineProperty(exports, "SHOW_CHILD", {
enumerable: true,
get: function () {
return _strategyUtil.SHOW_CHILD;
}
});
Object.defineProperty(exports, "SHOW_PARENT", {
enumerable: true,
get: function () {
return _strategyUtil.SHOW_PARENT;
}
});
Object.defineProperty(exports, "TreeNode", {
enumerable: true,
get: function () {
return _TreeNode.default;
}
});
exports.default = void 0;
var _TreeSelect = _interopRequireDefault(require("./TreeSelect"));
var _TreeNode = _interopRequireDefault(require("./TreeNode"));
var _strategyUtil = require("./utils/strategyUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = exports.default = _TreeSelect.default;
@@ -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,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
@@ -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,131 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.convertChildrenToData = convertChildrenToData;
exports.fillAdditionalInfo = fillAdditionalInfo;
exports.fillLegacyProps = fillLegacyProps;
var React = _interopRequireWildcard(require("react"));
var _toArray = _interopRequireDefault(require("@rc-component/util/lib/Children/toArray"));
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _TreeNode = _interopRequireDefault(require("../TreeNode"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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 convertChildrenToData(nodes) {
return (0, _toArray.default)(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);
}
function fillLegacyProps(dataNode) {
if (!dataNode) {
return dataNode;
}
const cloneNode = {
...dataNode
};
if (!('props' in cloneNode)) {
Object.defineProperty(cloneNode, 'props', {
get() {
(0, _warning.default)(false, 'New `rc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.');
return cloneNode;
}
});
}
return cloneNode;
}
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.default, 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() {
(0, _warning.default)(false, '`triggerNode` is deprecated. Please consider decoupling data with node.');
generateMap();
return triggerNode;
}
});
Object.defineProperty(extra, 'allCheckedNodes', {
get() {
(0, _warning.default)(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,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SHOW_PARENT = exports.SHOW_CHILD = exports.SHOW_ALL = void 0;
exports.formatStrategyValues = formatStrategyValues;
var _valueUtil = require("./valueUtil");
const SHOW_ALL = exports.SHOW_ALL = 'SHOW_ALL';
const SHOW_PARENT = exports.SHOW_PARENT = 'SHOW_PARENT';
const SHOW_CHILD = exports.SHOW_CHILD = 'SHOW_CHILD';
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
}) => (0, _valueUtil.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 || (0, _valueUtil.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,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toArray = exports.isNil = exports.isCheckDisabled = exports.getAllKeys = exports.fillFieldNames = void 0;
const toArray = value => Array.isArray(value) ? value : value !== undefined ? [value] : [];
exports.toArray = toArray;
const fillFieldNames = fieldNames => {
const {
label,
value,
children
} = fieldNames || {};
return {
_title: label ? [label] : ['title', 'label'],
value: value || 'value',
key: value || 'value',
children: children || 'children'
};
};
exports.fillFieldNames = fillFieldNames;
const isCheckDisabled = node => !node || node.disabled || node.disableCheckbox || node.checkable === false;
exports.isCheckDisabled = isCheckDisabled;
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;
};
exports.getAllKeys = getAllKeys;
const isNil = val => val === null || val === undefined;
exports.isNil = isNil;
@@ -0,0 +1,5 @@
import type { TreeSelectProps } from '../TreeSelect';
declare function warningProps(props: TreeSelectProps & {
searchPlaceholder?: string;
}): void;
export default warningProps;
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _valueUtil = require("./valueUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function warningProps(props) {
const {
searchPlaceholder,
treeCheckStrictly,
treeCheckable,
labelInValue,
value,
multiple,
showCheckedStrategy,
maxCount
} = props;
(0, _warning.default)(!searchPlaceholder, '`searchPlaceholder` has been removed.');
if (treeCheckStrictly && labelInValue === false) {
(0, _warning.default)(false, '`treeCheckStrictly` will force set `labelInValue` to `true`.');
}
if (labelInValue || treeCheckStrictly) {
(0, _warning.default)((0, _valueUtil.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) {
(0, _warning.default)(!value || Array.isArray(value), '`value` should be an array when `TreeSelect` is checkable or multiple.');
} else {
(0, _warning.default)(!Array.isArray(value), '`value` should not be array when `TreeSelect` is single mode.');
}
if (maxCount && (showCheckedStrategy === 'SHOW_ALL' && !treeCheckStrictly || showCheckedStrategy === 'SHOW_PARENT')) {
(0, _warning.default)(false, '`maxCount` not work with `showCheckedStrategy=SHOW_ALL` (when `treeCheckStrictly=false`) or `showCheckedStrategy=SHOW_PARENT`.');
}
}
var _default = exports.default = warningProps;
+88
View File
@@ -0,0 +1,88 @@
{
"name": "@rc-component/tree-select",
"version": "1.8.0",
"description": "tree-select ui component for react",
"keywords": [
"react",
"react-component",
"react-tree-select",
"tree-select"
],
"homepage": "https://github.com/react-component/tree-select",
"bugs": {
"url": "https://github.com/react-component/tree-select/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/react-component/tree-select.git"
},
"license": "MIT",
"author": "smith3816@gmail.com",
"main": "./lib/index",
"module": "./es/index",
"files": [
"es",
"lib",
"assets/*.less",
"assets/*.css",
"assets/*.png",
"assets/*.gif"
],
"scripts": {
"build": "dumi build",
"compile": "father build",
"lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
"now-build": "npm run build",
"prepare": "husky && dumi setup",
"prepublishOnly": "npm run compile && rc-np",
"prettier": "prettier '{src,tests}/**/*.{ts,tsx}' 'tests/**/*.js' --write",
"start": "dumi dev",
"test": "rc-test"
},
"lint-staged": {
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@rc-component/select": "~1.6.0",
"@rc-component/tree": "~1.2.0",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"devDependencies": {
"@rc-component/dialog": "^1.2.0",
"@rc-component/father-plugin": "^2.0.2",
"@rc-component/form": "^1.4.0",
"@rc-component/np": "^1.0.3",
"@rc-component/trigger": "^3.0.0",
"@rc-component/virtual-list": "^1.0.1",
"@testing-library/react": "^12.1.5",
"@types/jest": "^29.5.13",
"@types/node": "^22.7.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^19.0.1",
"@types/warning": "^3.0.3",
"@umijs/fabric": "^4.0.1",
"cheerio": "1.0.0-rc.12",
"cross-env": "^7.0.3",
"dumi": "^2.4.12",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.8",
"enzyme-to-json": "^3.6.2",
"eslint": "^8.57.1",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-unicorn": "^56.0.0",
"father": "^4.5.0",
"glob": "^11.0.0",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"rc-test": "^7.1.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"typescript": "^5.6.3"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
}
}