index.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. /* eslint-disable max-lines-per-function */
  2. import React, { MouseEvent } from 'react';
  3. import cls from 'classnames';
  4. import PropTypes from 'prop-types';
  5. import ConfigContext from '../configProvider/context';
  6. import TreeFoundation, { TreeAdapter } from '@douyinfe/semi-foundation/tree/foundation';
  7. import {
  8. convertDataToEntities,
  9. flattenTreeData,
  10. calcExpandedKeysForValues,
  11. calcMotionKeys,
  12. convertJsonToData,
  13. findKeysForValues,
  14. calcCheckedKeys,
  15. calcExpandedKeys,
  16. filterTreeData,
  17. normalizeValue,
  18. updateKeys,
  19. calcDisabledKeys
  20. } from '@douyinfe/semi-foundation/tree/treeUtil';
  21. import { cssClasses, strings } from '@douyinfe/semi-foundation/tree/constants';
  22. import BaseComponent from '../_base/baseComponent';
  23. import { isEmpty, isEqual, get, isFunction } from 'lodash-es';
  24. import { cloneDeep } from './treeUtil';
  25. import Input from '../input/index';
  26. import { FixedSizeList as VirtualList } from 'react-window';
  27. import AutoSizer from './autoSizer';
  28. import TreeContext from './treeContext';
  29. import TreeNode from './treeNode';
  30. import NodeList from './nodeList';
  31. import LocaleConsumer from '../locale/localeConsumer';
  32. import '@douyinfe/semi-foundation/tree/tree.scss';
  33. import { IconSearch } from '@douyinfe/semi-icons';
  34. import { Locale as LocaleObject } from '../locale/interface';
  35. import {
  36. TreeProps,
  37. TreeState,
  38. TreeNodeProps,
  39. TreeNodeData,
  40. FlattenNode,
  41. KeyEntity,
  42. OptionProps
  43. } from './interface';
  44. export * from './interface';
  45. export { AutoSizerProps } from './autoSizer';
  46. const prefixcls = cssClasses.PREFIX;
  47. class Tree extends BaseComponent<TreeProps, TreeState> {
  48. static contextType = ConfigContext;
  49. static propTypes = {
  50. blockNode: PropTypes.bool,
  51. className: PropTypes.string,
  52. showClear: PropTypes.bool,
  53. defaultExpandAll: PropTypes.bool,
  54. defaultExpandedKeys: PropTypes.array,
  55. defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  56. directory: PropTypes.bool,
  57. disabled: PropTypes.bool,
  58. emptyContent: PropTypes.node,
  59. expandAll: PropTypes.bool,
  60. expandedKeys: PropTypes.array,
  61. filterTreeNode: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
  62. icon: PropTypes.node,
  63. onChangeWithObject: PropTypes.bool,
  64. motion: PropTypes.bool,
  65. multiple: PropTypes.bool,
  66. onChange: PropTypes.func,
  67. onExpand: PropTypes.func,
  68. onSearch: PropTypes.func,
  69. onSelect: PropTypes.func,
  70. onContextMenu: PropTypes.func,
  71. onDoubleClick: PropTypes.func,
  72. searchClassName: PropTypes.string,
  73. searchPlaceholder: PropTypes.string,
  74. searchStyle: PropTypes.object,
  75. selectedKey: PropTypes.string,
  76. showFilteredOnly: PropTypes.bool,
  77. style: PropTypes.object,
  78. treeData: PropTypes.arrayOf(
  79. PropTypes.shape({
  80. key: PropTypes.string.isRequired,
  81. value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  82. label: PropTypes.any,
  83. isLeaf: PropTypes.bool,
  84. })
  85. ),
  86. treeDataSimpleJson: PropTypes.object,
  87. treeNodeFilterProp: PropTypes.string,
  88. value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
  89. virtualize: PropTypes.object,
  90. autoExpandParent: PropTypes.bool,
  91. expandAction: PropTypes.oneOf(strings.EXPAND_ACTION),
  92. searchRender: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
  93. renderLabel: PropTypes.func,
  94. renderFullLabel: PropTypes.func,
  95. leafOnly: PropTypes.bool,
  96. loadedKeys: PropTypes.array,
  97. loadData: PropTypes.func,
  98. onLoad: PropTypes.func,
  99. disableStrictly: PropTypes.bool,
  100. draggable: PropTypes.bool,
  101. autoExpandWhenDragEnter: PropTypes.bool,
  102. hideDraggingNode: PropTypes.bool,
  103. renderDraggingNode: PropTypes.func,
  104. onDragEnd: PropTypes.func,
  105. onDragEnter: PropTypes.func,
  106. onDragLeave: PropTypes.func,
  107. onDragOver: PropTypes.func,
  108. onDragStart: PropTypes.func,
  109. onDrop: PropTypes.func,
  110. labelEllipsis: PropTypes.bool,
  111. };
  112. static defaultProps = {
  113. showClear: true,
  114. disabled: false,
  115. blockNode: true,
  116. multiple: false,
  117. filterTreeNode: false,
  118. autoExpandParent: false,
  119. treeNodeFilterProp: 'label',
  120. defaultExpandAll: false,
  121. expandAll: false,
  122. onChangeWithObject: false,
  123. motion: true,
  124. leafOnly: false,
  125. showFilteredOnly: false,
  126. expandAction: false,
  127. disableStrictly: false,
  128. draggable: false,
  129. autoExpandWhenDragEnter: true,
  130. };
  131. static TreeNode: typeof TreeNode;
  132. inputRef: React.RefObject<typeof Input>;
  133. optionsRef: React.RefObject<any>;
  134. dragNode: any;
  135. onNodeClick: any;
  136. onMotionEnd: any;
  137. constructor(props: TreeProps) {
  138. super(props);
  139. this.state = {
  140. inputValue: '',
  141. keyEntities: {},
  142. treeData: [],
  143. flattenNodes: [],
  144. selectedKeys: [],
  145. checkedKeys: new Set(),
  146. halfCheckedKeys: new Set(),
  147. motionKeys: new Set([]),
  148. motionType: 'hide',
  149. expandedKeys: new Set(props.expandedKeys),
  150. filteredKeys: new Set(),
  151. filteredExpandedKeys: new Set(),
  152. filteredShownKeys: new Set(),
  153. prevProps: null,
  154. loadedKeys: new Set(),
  155. loadingKeys: new Set(),
  156. cachedFlattenNodes: undefined,
  157. cachedKeyValuePairs: {},
  158. disabledKeys: new Set(),
  159. dragging: false,
  160. dragNodesKeys: new Set(),
  161. dragOverNodeKey: null,
  162. dropPosition: null,
  163. };
  164. this.inputRef = React.createRef();
  165. this.optionsRef = React.createRef();
  166. this.foundation = new TreeFoundation(this.adapter);
  167. this.dragNode = null;
  168. }
  169. static getDerivedStateFromProps(props: TreeProps, prevState: TreeState) {
  170. const { prevProps } = prevState;
  171. let treeData;
  172. let keyEntities = prevState.keyEntities || {};
  173. let valueEntities = prevState.cachedKeyValuePairs || {};
  174. const isSeaching = Boolean(props.filterTreeNode && prevState.inputValue && prevState.inputValue.length);
  175. const newState: Partial<TreeState> = {
  176. prevProps: props,
  177. };
  178. // Accept a props field as a parameter to determine whether to update the field
  179. const needUpdate = (name: string) => {
  180. const firstInProps = !prevProps && name in props;
  181. const nameHasChange = prevProps && !isEqual(prevProps[name], props[name]);
  182. return firstInProps || nameHasChange;
  183. };
  184. // Determine whether treeData has changed
  185. const needUpdateData = () => {
  186. const firstInProps = !prevProps && 'treeData' in props;
  187. const treeDataHasChange = prevProps && prevProps.treeData !== props.treeData;
  188. return firstInProps || treeDataHasChange;
  189. };
  190. // Update the data of tree in state
  191. if (needUpdate('treeData') || (props.draggable && needUpdateData())) {
  192. // eslint-disable-next-line prefer-destructuring
  193. treeData = props.treeData;
  194. newState.treeData = treeData;
  195. const entitiesMap = convertDataToEntities(treeData);
  196. newState.keyEntities = {
  197. ...entitiesMap.keyEntities,
  198. };
  199. keyEntities = newState.keyEntities;
  200. newState.cachedKeyValuePairs = { ...entitiesMap.valueEntities };
  201. valueEntities = newState.cachedKeyValuePairs;
  202. } else if (needUpdate('treeDataSimpleJson')) {
  203. // Convert treeDataSimpleJson to treeData
  204. treeData = convertJsonToData(props.treeDataSimpleJson);
  205. newState.treeData = treeData;
  206. const entitiesMap = convertDataToEntities(treeData);
  207. newState.keyEntities = {
  208. ...entitiesMap.keyEntities,
  209. };
  210. keyEntities = newState.keyEntities;
  211. newState.cachedKeyValuePairs = { ...entitiesMap.valueEntities };
  212. valueEntities = newState.cachedKeyValuePairs;
  213. }
  214. // If treeData keys changes, we won't show animation
  215. if (treeData && props.motion) {
  216. if (prevProps && props.motion) {
  217. newState.motionKeys = new Set([]);
  218. newState.motionType = null;
  219. }
  220. }
  221. const expandAllWhenDataChange = (needUpdate('treeDataSimpleJson') || needUpdate('treeData')) && props.expandAll;
  222. if (!isSeaching) {
  223. // Update expandedKeys
  224. if (needUpdate('expandedKeys') || (prevProps && needUpdate('autoExpandParent'))) {
  225. newState.expandedKeys = calcExpandedKeys(
  226. props.expandedKeys,
  227. keyEntities,
  228. props.autoExpandParent || !prevProps
  229. );
  230. // only show animation when treeData does not change
  231. if (prevProps && props.motion && !treeData) {
  232. const { motionKeys, motionType } = calcMotionKeys(
  233. prevState.expandedKeys,
  234. newState.expandedKeys,
  235. keyEntities
  236. );
  237. newState.motionKeys = new Set(motionKeys);
  238. newState.motionType = motionType;
  239. if (motionType === 'hide') {
  240. // cache flatten nodes: expandedKeys changed may not be triggered by interaction
  241. newState.cachedFlattenNodes = cloneDeep(prevState.flattenNodes);
  242. }
  243. }
  244. } else if ((!prevProps && (props.defaultExpandAll || props.expandAll)) || expandAllWhenDataChange) {
  245. newState.expandedKeys = new Set(Object.keys(keyEntities));
  246. } else if (!prevProps && props.defaultExpandedKeys) {
  247. newState.expandedKeys = calcExpandedKeys(props.defaultExpandedKeys, keyEntities);
  248. } else if (!prevProps && props.defaultValue) {
  249. newState.expandedKeys = calcExpandedKeysForValues(
  250. props.defaultValue,
  251. keyEntities,
  252. props.multiple,
  253. valueEntities
  254. );
  255. } else if (!prevProps && props.value) {
  256. newState.expandedKeys = calcExpandedKeysForValues(
  257. props.value,
  258. keyEntities,
  259. props.multiple,
  260. valueEntities
  261. );
  262. }
  263. if (!newState.expandedKeys) {
  264. delete newState.expandedKeys;
  265. }
  266. // Update flattenNodes
  267. if (treeData || newState.expandedKeys) {
  268. const flattenNodes = flattenTreeData(
  269. treeData || prevState.treeData,
  270. newState.expandedKeys || prevState.expandedKeys
  271. );
  272. newState.flattenNodes = flattenNodes;
  273. }
  274. } else {
  275. let filteredState;
  276. // treeData changed while searching
  277. if (treeData) {
  278. // Get filter data
  279. filteredState = filterTreeData({
  280. treeData,
  281. inputValue: prevState.inputValue,
  282. filterTreeNode: props.filterTreeNode,
  283. filterProps: props.treeNodeFilterProp,
  284. showFilteredOnly: props.showFilteredOnly,
  285. keyEntities: newState.keyEntities,
  286. prevExpandedKeys: [...prevState.filteredExpandedKeys],
  287. });
  288. newState.flattenNodes = filteredState.flattenNodes;
  289. newState.motionKeys = new Set([]);
  290. newState.filteredKeys = filteredState.filteredKeys;
  291. newState.filteredShownKeys = filteredState.filteredShownKeys;
  292. newState.filteredExpandedKeys = filteredState.filteredExpandedKeys;
  293. }
  294. // expandedKeys changed while searching
  295. if (props.expandedKeys) {
  296. newState.filteredExpandedKeys = calcExpandedKeys(
  297. props.expandedKeys,
  298. keyEntities,
  299. props.autoExpandParent || !prevProps
  300. );
  301. if (prevProps && props.motion) {
  302. const prevKeys = prevState ? prevState.filteredExpandedKeys : new Set([]);
  303. // only show animation when treeData does not change
  304. if (!treeData) {
  305. const motionResult = calcMotionKeys(
  306. prevKeys,
  307. newState.filteredExpandedKeys,
  308. keyEntities
  309. );
  310. let { motionKeys } = motionResult;
  311. const { motionType } = motionResult;
  312. if (props.showFilteredOnly) {
  313. motionKeys = motionKeys.filter(key => prevState.filteredShownKeys.has(key));
  314. }
  315. if (motionType === 'hide') {
  316. // cache flatten nodes: expandedKeys changed may not be triggered by interaction
  317. newState.cachedFlattenNodes = cloneDeep(prevState.flattenNodes);
  318. }
  319. newState.motionKeys = new Set(motionKeys);
  320. newState.motionType = motionType;
  321. }
  322. }
  323. newState.flattenNodes = flattenTreeData(
  324. treeData || prevState.treeData,
  325. newState.filteredExpandedKeys || prevState.filteredExpandedKeys,
  326. props.showFilteredOnly && prevState.filteredShownKeys
  327. );
  328. }
  329. }
  330. // Handle single selection and multiple selection in controlled mode
  331. const withObject = props.onChangeWithObject;
  332. const isMultiple = props.multiple;
  333. if (!isMultiple) {
  334. // When getting single selection, the selected node
  335. if (needUpdate('value')) {
  336. newState.selectedKeys = findKeysForValues(
  337. // In both cases whether withObject is turned on, the value is standardized to string
  338. normalizeValue(props.value, withObject),
  339. valueEntities,
  340. isMultiple
  341. );
  342. } else if (!prevProps && props.defaultValue) {
  343. newState.selectedKeys = findKeysForValues(
  344. normalizeValue(props.defaultValue, withObject),
  345. valueEntities,
  346. isMultiple
  347. );
  348. } else if (treeData) {
  349. // If `treeData` changed, we also need check it
  350. if (props.value) {
  351. newState.selectedKeys = findKeysForValues(
  352. normalizeValue(props.value, withObject) || '',
  353. valueEntities,
  354. isMultiple
  355. );
  356. }
  357. }
  358. } else {
  359. let checkedKeyValues;
  360. // Get the selected node during multiple selection
  361. if (needUpdate('value')) {
  362. checkedKeyValues = findKeysForValues(
  363. normalizeValue(props.value, withObject),
  364. valueEntities,
  365. isMultiple
  366. );
  367. } else if (!prevProps && props.defaultValue) {
  368. checkedKeyValues = findKeysForValues(
  369. normalizeValue(props.defaultValue, withObject),
  370. valueEntities,
  371. isMultiple
  372. );
  373. } else if (treeData) {
  374. // If `treeData` changed, we also need check it
  375. if (props.value) {
  376. checkedKeyValues = findKeysForValues(
  377. normalizeValue(props.value, withObject) || [],
  378. valueEntities,
  379. isMultiple
  380. );
  381. } else {
  382. checkedKeyValues = updateKeys(prevState.checkedKeys, keyEntities);
  383. }
  384. }
  385. if (checkedKeyValues) {
  386. const { checkedKeys, halfCheckedKeys } = calcCheckedKeys(checkedKeyValues, keyEntities);
  387. newState.checkedKeys = checkedKeys;
  388. newState.halfCheckedKeys = halfCheckedKeys;
  389. }
  390. }
  391. // update loadedKeys
  392. if (needUpdate('loadedKeys')) {
  393. newState.loadedKeys = new Set(props.loadedKeys);
  394. }
  395. // update disableStrictly
  396. if (treeData && props.disableStrictly) {
  397. newState.disabledKeys = calcDisabledKeys(keyEntities);
  398. }
  399. return newState;
  400. }
  401. get adapter(): TreeAdapter {
  402. const filterAdapter: Pick<TreeAdapter, 'updateInputValue' | 'focusInput'> = {
  403. updateInputValue: value => {
  404. this.setState({ inputValue: value });
  405. },
  406. focusInput: () => {
  407. if (this.inputRef && this.inputRef.current) {
  408. (this.inputRef.current as any).focus();
  409. }
  410. },
  411. };
  412. return {
  413. ...super.adapter,
  414. ...filterAdapter,
  415. updateState: states => {
  416. this.setState({ ...states } as TreeState);
  417. },
  418. notifyExpand: (expandedKeys, { expanded: bool, node }) => {
  419. this.props.onExpand && this.props.onExpand([...expandedKeys], { expanded: bool, node });
  420. if (bool && this.props.loadData) {
  421. this.onNodeLoad(node);
  422. }
  423. },
  424. notifySelect: (selectKey, bool, node) => {
  425. this.props.onSelect && this.props.onSelect(selectKey, bool, node);
  426. },
  427. notifyChange: value => {
  428. this.props.onChange && this.props.onChange(value);
  429. },
  430. notifySearch: input => {
  431. this.props.onSearch && this.props.onSearch(input);
  432. },
  433. notifyRightClick: (e, node) => {
  434. this.props.onContextMenu && this.props.onContextMenu(e, node);
  435. },
  436. notifyDoubleClick: (e, node) => {
  437. this.props.onDoubleClick && this.props.onDoubleClick(e, node);
  438. },
  439. cacheFlattenNodes: bool => {
  440. this.setState({ cachedFlattenNodes: bool ? cloneDeep(this.state.flattenNodes) : undefined });
  441. },
  442. setDragNode: treeNode => {
  443. this.dragNode = treeNode;
  444. },
  445. };
  446. }
  447. search = (value: string) => {
  448. this.foundation.handleInputChange(value);
  449. };
  450. renderInput() {
  451. const {
  452. searchClassName,
  453. searchStyle,
  454. searchRender,
  455. searchPlaceholder,
  456. showClear
  457. } = this.props;
  458. const inputcls = cls(`${prefixcls}-input`);
  459. const { inputValue } = this.state;
  460. const inputProps = {
  461. value: inputValue,
  462. className: inputcls,
  463. onChange: (value: string) => this.search(value),
  464. prefix: <IconSearch />,
  465. showClear,
  466. placeholder: searchPlaceholder,
  467. };
  468. const wrapperCls = cls(`${prefixcls}-search-wrapper`, searchClassName);
  469. return (
  470. <div className={wrapperCls} style={searchStyle}>
  471. <LocaleConsumer componentName="Tree">
  472. {(locale: LocaleObject) => {
  473. inputProps.placeholder = searchPlaceholder || get(locale, 'searchPlaceholder');
  474. if (isFunction(searchRender)) {
  475. return searchRender({ ...inputProps });
  476. }
  477. if (searchRender === false) {
  478. return null;
  479. }
  480. return (
  481. <Input
  482. ref={this.inputRef as any}
  483. {...inputProps}
  484. />
  485. );
  486. }}
  487. </LocaleConsumer>
  488. </div>
  489. );
  490. }
  491. renderEmpty = () => {
  492. const { emptyContent } = this.props;
  493. if (emptyContent) {
  494. return <TreeNode empty emptyContent={this.props.emptyContent} />;
  495. } else {
  496. return (
  497. <LocaleConsumer componentName="Tree">
  498. {(locale: LocaleObject) => <TreeNode empty emptyContent={get(locale, 'emptyText')} />}
  499. </LocaleConsumer>
  500. );
  501. }
  502. };
  503. onNodeSelect = (e: MouseEvent, treeNode: TreeNodeProps) => {
  504. this.foundation.handleNodeSelect(e, treeNode);
  505. };
  506. onNodeLoad = (data: TreeNodeData) => (
  507. new Promise(resolve => {
  508. // We need to get the latest state of loading/loaded keys
  509. this.setState(({ loadedKeys = new Set([]), loadingKeys = new Set([]) }) => (
  510. this.foundation.handleNodeLoad(loadedKeys, loadingKeys, data, resolve)
  511. ));
  512. })
  513. );
  514. onNodeCheck = (e: MouseEvent, treeNode: TreeNodeProps) => {
  515. this.foundation.handleNodeSelect(e, treeNode);
  516. };
  517. onNodeExpand = (e: MouseEvent, treeNode: TreeNodeProps) => {
  518. this.foundation.handleNodeExpand(e, treeNode);
  519. };
  520. onNodeRightClick = (e: MouseEvent, treeNode: TreeNodeProps) => {
  521. this.foundation.handleNodeRightClick(e, treeNode);
  522. };
  523. onNodeDoubleClick = (e: MouseEvent, treeNode: TreeNodeProps) => {
  524. this.foundation.handleNodeDoubleClick(e, treeNode);
  525. };
  526. onNodeDragStart = (e: React.DragEvent<HTMLDivElement>, treeNode: TreeNodeProps) => {
  527. this.foundation.handleNodeDragStart(e, treeNode);
  528. };
  529. onNodeDragEnter = (e: React.DragEvent<HTMLDivElement>, treeNode: TreeNodeProps) => {
  530. this.foundation.handleNodeDragEnter(e, treeNode, this.dragNode);
  531. };
  532. onNodeDragOver = (e: React.DragEvent<HTMLDivElement>, treeNode: TreeNodeProps) => {
  533. this.foundation.handleNodeDragOver(e, treeNode, this.dragNode);
  534. };
  535. onNodeDragLeave = (e: React.DragEvent<HTMLDivElement>, treeNode: TreeNodeProps) => {
  536. this.foundation.handleNodeDragLeave(e, treeNode);
  537. };
  538. onNodeDragEnd = (e: React.DragEvent<HTMLDivElement>, treeNode: TreeNodeProps) => {
  539. this.foundation.handleNodeDragEnd(e, treeNode);
  540. };
  541. onNodeDrop = (e: React.DragEvent<HTMLDivElement>, treeNode: TreeNodeProps) => {
  542. this.foundation.handleNodeDrop(e, treeNode, this.dragNode);
  543. };
  544. getTreeNodeRequiredProps = () => {
  545. const { expandedKeys, selectedKeys, checkedKeys, halfCheckedKeys, keyEntities, filteredKeys } = this.state;
  546. return {
  547. expandedKeys: expandedKeys || new Set(),
  548. selectedKeys: selectedKeys || [],
  549. checkedKeys: checkedKeys || new Set(),
  550. halfCheckedKeys: halfCheckedKeys || new Set(),
  551. filteredKeys: filteredKeys || new Set(),
  552. keyEntities,
  553. };
  554. };
  555. getTreeNodeKey = (treeNode: TreeNodeData) => {
  556. const { data } = treeNode;
  557. const { key } = data;
  558. return key;
  559. };
  560. renderTreeNode = (treeNode: FlattenNode, ind?: number, style?: React.CSSProperties) => {
  561. const { data } = treeNode;
  562. const { key } = data;
  563. const treeNodeProps = this.foundation.getTreeNodeProps(key);
  564. if (!treeNodeProps) {
  565. return null;
  566. }
  567. return <TreeNode {...treeNodeProps} {...data} key={key} data={data} style={isEmpty(style) ? {} : style} />;
  568. };
  569. itemKey = (index: number, data: KeyEntity) => {
  570. // Find the item at the specified index.
  571. const item = data[index];
  572. // Return a value that uniquely identifies this item.
  573. return item.key;
  574. };
  575. renderNodeList() {
  576. const { flattenNodes, cachedFlattenNodes, motionKeys, motionType } = this.state;
  577. const { virtualize, motion } = this.props;
  578. const { direction } = this.context;
  579. if (isEmpty(flattenNodes)) {
  580. return undefined;
  581. }
  582. if (!virtualize || isEmpty(virtualize)) {
  583. return (
  584. <NodeList
  585. flattenNodes={flattenNodes}
  586. flattenList={cachedFlattenNodes}
  587. motionKeys={motion ? motionKeys : new Set([])}
  588. motionType={motionType}
  589. onMotionEnd={this.onMotionEnd}
  590. renderTreeNode={this.renderTreeNode}
  591. />
  592. );
  593. }
  594. const option = ({ index, style, data }: OptionProps) => (
  595. this.renderTreeNode(data[index], index, style)
  596. );
  597. return (
  598. <AutoSizer defaultHeight={virtualize.height} defaultWidth={virtualize.width}>
  599. {({ height, width }: { width: string | number; height: string | number }) => (
  600. <VirtualList
  601. itemCount={flattenNodes.length}
  602. itemSize={virtualize.itemSize}
  603. height={height}
  604. width={width}
  605. itemKey={this.itemKey}
  606. itemData={flattenNodes as any}
  607. className={`${prefixcls}-virtual-list`}
  608. style={{ direction }}
  609. >
  610. {option}
  611. </VirtualList>
  612. )}
  613. </AutoSizer>
  614. );
  615. }
  616. render() {
  617. const {
  618. keyEntities,
  619. motionKeys,
  620. motionType,
  621. inputValue,
  622. filteredKeys,
  623. dragOverNodeKey,
  624. dropPosition,
  625. } = this.state;
  626. const {
  627. blockNode,
  628. className,
  629. style,
  630. filterTreeNode,
  631. disabled,
  632. icon,
  633. directory,
  634. multiple,
  635. showFilteredOnly,
  636. motion,
  637. expandAction,
  638. loadData,
  639. renderLabel,
  640. draggable,
  641. renderFullLabel,
  642. labelEllipsis,
  643. virtualize,
  644. } = this.props;
  645. const wrapperCls = cls(`${prefixcls}-wrapper`, className);
  646. const listCls = cls(`${prefixcls}-option-list`, {
  647. [`${prefixcls}-option-list-block`]: blockNode,
  648. });
  649. const searchNoRes = Boolean(inputValue) && !filteredKeys.size;
  650. const noData = isEmpty(keyEntities) || (showFilteredOnly && searchNoRes);
  651. return (
  652. <TreeContext.Provider
  653. value={{
  654. treeDisabled: disabled,
  655. treeIcon: icon,
  656. motion,
  657. motionKeys,
  658. motionType,
  659. filterTreeNode,
  660. keyEntities,
  661. onNodeClick: this.onNodeClick,
  662. onNodeExpand: this.onNodeExpand,
  663. onNodeSelect: this.onNodeSelect,
  664. onNodeCheck: this.onNodeCheck,
  665. onNodeRightClick: this.onNodeRightClick,
  666. onNodeDoubleClick: this.onNodeDoubleClick,
  667. renderTreeNode: this.renderTreeNode,
  668. onNodeDragStart: this.onNodeDragStart,
  669. onNodeDragEnter: this.onNodeDragEnter,
  670. onNodeDragOver: this.onNodeDragOver,
  671. onNodeDragLeave: this.onNodeDragLeave,
  672. onNodeDragEnd: this.onNodeDragEnd,
  673. onNodeDrop: this.onNodeDrop,
  674. expandAction,
  675. directory,
  676. multiple,
  677. showFilteredOnly,
  678. isSearching: Boolean(inputValue),
  679. loadData,
  680. onNodeLoad: this.onNodeLoad,
  681. renderLabel,
  682. draggable,
  683. renderFullLabel,
  684. dragOverNodeKey,
  685. dropPosition,
  686. labelEllipsis: typeof labelEllipsis === 'undefined' ? virtualize : labelEllipsis,
  687. }}
  688. >
  689. <div className={wrapperCls} role="list-box" style={style}>
  690. {filterTreeNode ? this.renderInput() : null}
  691. <div className={listCls} role="tree">
  692. {noData ? this.renderEmpty() : this.renderNodeList()}
  693. </div>
  694. </div>
  695. </TreeContext.Provider>
  696. );
  697. }
  698. }
  699. Tree.TreeNode = TreeNode;
  700. export default Tree;