foundation.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. /**
  2. * The drag and drop handler implementation is referenced from rc-tree
  3. * https://github.com/react-component/tree
  4. */
  5. import { isUndefined, difference, pick, cloneDeep, get } from 'lodash';
  6. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  7. import {
  8. flattenTreeData,
  9. findDescendantKeys,
  10. findAncestorKeys,
  11. filter,
  12. normalizedArr,
  13. normalizeKeyList,
  14. getMotionKeys,
  15. calcCheckedKeysForChecked,
  16. calcCheckedKeysForUnchecked,
  17. calcCheckedKeys,
  18. getValueOrKey,
  19. getDragNodesKeys,
  20. calcDropRelativePosition,
  21. calcDropActualPosition
  22. } from './treeUtil';
  23. export interface BasicTreeNodeProps {
  24. [x: string]: any;
  25. expanded?: boolean;
  26. selected?: boolean;
  27. checked?: boolean;
  28. halfChecked?: boolean;
  29. active?: boolean;
  30. disabled?: boolean;
  31. loaded?: boolean;
  32. loading?: boolean;
  33. isLeaf?: boolean;
  34. pos?: string;
  35. children?: BasicTreeNodeData[];
  36. icon?: any;
  37. directory?: boolean;
  38. selectedKey?: string;
  39. motionKey?: string[] | string;
  40. eventKey?: string
  41. }
  42. export interface BasicTreeNodeData {
  43. [x: string]: any;
  44. key: string;
  45. value?: number | string;
  46. label?: any;
  47. icon?: any;
  48. disabled?: boolean;
  49. isLeaf?: boolean;
  50. children?: BasicTreeNodeData[]
  51. }
  52. export interface BasicKeyEntities {
  53. [key: string]: BasicKeyEntity
  54. }
  55. export interface BasicKeyEntity {
  56. children?: BasicKeyEntities;
  57. data?: BasicTreeNodeData;
  58. ind?: number;
  59. key?: string;
  60. level?: number;
  61. parent?: undefined | BasicKeyEntity;
  62. parentPos?: null | string;
  63. pos?: string
  64. }
  65. export interface BasicDragTreeNode extends BasicTreeNodeData {
  66. expanded: boolean;
  67. /**
  68. * The positional relationship of the current node in the entire
  69. * treeData, such as the 0th node of the 2nd node of the 1st node
  70. * of the 0th layer: '0-1-2-0'
  71. */
  72. pos: string
  73. }
  74. export interface BasicFlattenNode {
  75. _innerDataTag?: boolean;
  76. children?: BasicFlattenNode[];
  77. data?: BasicTreeNodeData;
  78. key?: string;
  79. label?: any;
  80. parent?: null | BasicFlattenNode;
  81. pos?: string;
  82. value?: string
  83. }
  84. export interface BasicDragProps {
  85. event: any;
  86. node: BasicDragTreeNode
  87. }
  88. export interface BasicDragEnterProps extends BasicDragProps {
  89. expandedKeys?: string[]
  90. }
  91. export type ExpandAction = false | 'click' | 'doubleClick';
  92. export type BasicValue = string | number | BasicTreeNodeData | Array<BasicTreeNodeData | string | number>;
  93. export interface BasicOnDragProps {
  94. event: any;
  95. node: BasicDragTreeNode;
  96. dragNode: BasicDragTreeNode;
  97. dragNodesKeys: string[];
  98. /**
  99. * dropPosition represents the position of the dragged node being
  100. * dropped in the current level. If inserted before the 0th node
  101. * of the same level, it is -1, after the 0th node, it is 1, and
  102. * it is 0 when it falls on it. And so on. With dropToGap, a more
  103. * complete judgment can be obtained.
  104. */
  105. dropPosition: number;
  106. /**
  107. * Indicates whether the dragged node is dropped between nodes, if
  108. * it is false, it is dropped above a node
  109. */
  110. dropToGap: boolean
  111. }
  112. export interface BasicRenderFullLabelProps {
  113. /* Click the callback of the entire row to control the expansion behavior and selection */
  114. onClick: (e: any) => void;
  115. /* Right-click the callback for the entire row */
  116. onContextMenu: (e: any) => void;
  117. /* Double-click the entire line of callback */
  118. onDoubleClick: (e: any) => void;
  119. /* Class name, including built-in styles such as indentation, expand button, filter, disable, select, etc. */
  120. className: string;
  121. /* Expand callback */
  122. onExpand: (e: any) => void;
  123. /* The original data of the row */
  124. data: BasicTreeNodeData;
  125. /* The level of the line can be used to customize the indentation value */
  126. level: number;
  127. /* The style required for virtualization, if virtualization is used, the style must be assigned to the DOM element */
  128. style: any;
  129. /* Multi-select click callback */
  130. onCheck: (e: any) => void;
  131. /* icon of Expand button */
  132. expandIcon: any;
  133. /* Selected state */
  134. checkStatus: {
  135. /* Whether to select in the multi-select state */
  136. checked: boolean;
  137. /* Whether to half-select in the multi-select state */
  138. halfChecked: boolean
  139. };
  140. /* Expand status */
  141. expandStatus: {
  142. /* Has it been expanded */
  143. expanded: boolean;
  144. /* Is it unfolding */
  145. loading: boolean
  146. };
  147. /* Whether the node meets the search conditions */
  148. filtered: boolean | undefined;
  149. /* Current search box input */
  150. searchWord: string | undefined
  151. }
  152. export interface BasicSearchRenderProps {
  153. className: string;
  154. placeholder: string;
  155. prefix: any;
  156. showClear?: boolean;
  157. value: string;
  158. onChange: (value: string) => void
  159. }
  160. export interface TreeDataSimpleJson {
  161. [x: string]: string | TreeDataSimpleJson
  162. }
  163. export interface Virtualize {
  164. itemSize: number;
  165. height?: number | string;
  166. width?: number | string
  167. }
  168. export type CheckRelation = 'related' | 'unRelated';
  169. export interface BasicTreeProps {
  170. autoExpandParent?: boolean;
  171. autoExpandWhenDragEnter?: boolean;
  172. blockNode?: boolean;
  173. children?: any;
  174. className?: string;
  175. expandAll?: boolean;
  176. defaultExpandAll?: boolean;
  177. defaultExpandedKeys?: string[];
  178. defaultValue?: BasicValue;
  179. directory?: boolean;
  180. disabled?: boolean;
  181. disableStrictly?: boolean;
  182. draggable?: boolean;
  183. emptyContent?: any;
  184. expandAction?: ExpandAction;
  185. expandedKeys?: string[];
  186. filterTreeNode?: boolean | ((inputValue: string, treeNodeString: string, data?: BasicTreeNodeData) => boolean);
  187. hideDraggingNode?: boolean;
  188. labelEllipsis?: boolean;
  189. leafOnly?: boolean;
  190. loadData?: (treeNode?: BasicTreeNodeData) => Promise<void>;
  191. loadedKeys?: string[];
  192. motion?: boolean;
  193. multiple?: boolean;
  194. onChange?: (value?: BasicValue) => void;
  195. onChangeWithObject?: boolean;
  196. onDoubleClick?: (e: any, node: BasicTreeNodeData) => void;
  197. onDragEnd?: (dragProps: BasicDragProps) => void;
  198. onDragEnter?: (dragEnterProps: BasicDragEnterProps) => void;
  199. onDragLeave?: (dragProps: BasicDragProps) => void;
  200. onDragOver?: (dragProps: BasicDragProps) => void;
  201. onDragStart?: (dragProps: BasicDragProps) => void;
  202. onDrop?: (onDragProps: BasicOnDragProps) => void;
  203. onExpand?: (expandedKeys: string[], expandedOtherProps: BasicExpandedOtherProps) => void;
  204. onLoad?: (loadedKeys?: Set<string>, treeNode?: BasicTreeNodeData) => void;
  205. onContextMenu?: (e: any, node: BasicTreeNodeData) => void;
  206. onSearch?: (sunInput: string, filteredExpandedKeys: string[]) => void;
  207. onSelect?: (selectedKeys: string, selected: boolean, selectedNode: BasicTreeNodeData) => void;
  208. preventScroll?: boolean;
  209. renderDraggingNode?: (nodeInstance: HTMLElement, node: BasicTreeNodeData) => HTMLElement;
  210. renderFullLabel?: (renderFullLabelProps: BasicRenderFullLabelProps) => any;
  211. renderLabel?: (label?: any, treeNode?: BasicTreeNodeData) => any;
  212. searchClassName?: string;
  213. searchPlaceholder?: string;
  214. searchRender?: ((searchRenderProps: BasicSearchRenderProps) => any) | false;
  215. searchStyle?: any;
  216. showClear?: boolean;
  217. showFilteredOnly?: boolean;
  218. style?: any;
  219. treeData?: BasicTreeNodeData[];
  220. treeDataSimpleJson?: TreeDataSimpleJson;
  221. treeNodeFilterProp?: string;
  222. value?: BasicValue;
  223. virtualize?: Virtualize;
  224. icon?: any;
  225. checkRelation?: CheckRelation;
  226. 'aria-label'?: string
  227. }
  228. /* Data maintained internally. At the React framework level, corresponding to state */
  229. export interface BasicTreeInnerData {
  230. /* The input content of the input box */
  231. inputValue: string;
  232. /* keyEntities */
  233. keyEntities: BasicKeyEntities;
  234. /* treeData */
  235. treeData: BasicTreeNodeData[];
  236. /* Expanded node */
  237. flattenNodes: BasicFlattenNode[];
  238. /* The selected node when single-selected */
  239. selectedKeys: string[];
  240. /* Select all nodes in multiple selection */
  241. checkedKeys: Set<string>;
  242. /* Half-selected node when multiple selection */
  243. halfCheckedKeys: Set<string>;
  244. /* real selected nodes in multiple selection */
  245. realCheckedKeys: Set<string>;
  246. /* Animation node */
  247. motionKeys: Set<string>;
  248. /* Animation type */
  249. motionType: string;
  250. /* Expand node */
  251. expandedKeys: Set<string>;
  252. /* Searched node */
  253. filteredKeys: Set<string>;
  254. /* The ancestor node expanded because of the searched node*/
  255. filteredExpandedKeys: Set<string>;
  256. /* Because of the searched node, the expanded ancestor node + the searched node + the descendant nodes of the searched node */
  257. filteredShownKeys: Set<string>;
  258. /* Prev props */
  259. prevProps: null | BasicTreeProps;
  260. /* loaded nodes */
  261. loadedKeys: Set<string>;
  262. /* loading nodes */
  263. loadingKeys: Set<string>;
  264. /* cache */
  265. cachedFlattenNodes: BasicFlattenNode[] | undefined;
  266. cachedKeyValuePairs: { [x: string]: string };
  267. /* Strictly disabled node */
  268. disabledKeys: Set<string>;
  269. /* Is dragging */
  270. dragging: boolean;
  271. /* Dragged node */
  272. dragNodesKeys: Set<string>;
  273. /* DragOver node */
  274. dragOverNodeKey: string[] | string | null;
  275. /* Drag position */
  276. dropPosition: number | null
  277. }
  278. export interface BasicExpandedOtherProps {
  279. expanded: boolean;
  280. node: BasicTreeNodeData
  281. }
  282. export interface TreeAdapter extends DefaultAdapter<BasicTreeProps, BasicTreeInnerData> {
  283. updateInputValue: (value: string) => void;
  284. focusInput: () => void;
  285. updateState: (states: Partial<BasicTreeInnerData>) => void;
  286. notifyExpand: (expandedKeys: Set<string>, { expanded, node }: BasicExpandedOtherProps) => void;
  287. notifySelect: (selectKey: string, bool: boolean, node: BasicTreeNodeData) => void;
  288. notifyChange: (value: BasicValue) => void;
  289. notifySearch: (input: string, filteredExpandedKeys: string[]) => void;
  290. notifyRightClick: (e: any, node: BasicTreeNodeData) => void;
  291. notifyDoubleClick: (e: any, node: BasicTreeNodeData) => void;
  292. cacheFlattenNodes: (bool: boolean) => void;
  293. setDragNode: (treeNode: any) => void
  294. }
  295. export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTreeProps, BasicTreeInnerData> {
  296. delayedDragEnterLogic: any;
  297. constructor(adapter: TreeAdapter) {
  298. super({
  299. ...adapter,
  300. });
  301. }
  302. _isMultiple() {
  303. return this.getProp('multiple');
  304. }
  305. _isAnimated() {
  306. return this.getProp('motion');
  307. }
  308. _isDisabled(treeNode: BasicTreeNodeProps = {}) {
  309. return this.getProp('disabled') || treeNode.disabled;
  310. }
  311. _isExpandControlled() {
  312. return !isUndefined(this.getProp('expandedKeys'));
  313. }
  314. _isLoadControlled() {
  315. return !isUndefined(this.getProp('loadedKeys'));
  316. }
  317. _isFilterable() {
  318. // filter can be boolean or function
  319. return Boolean(this.getProp('filterTreeNode'));
  320. }
  321. _showFilteredOnly() {
  322. const { inputValue } = this.getStates();
  323. const { showFilteredOnly } = this.getProps();
  324. return Boolean(inputValue) && showFilteredOnly;
  325. }
  326. getCopyFromState(items: string[] | string) {
  327. const res: Partial<BasicTreeInnerData> = {};
  328. normalizedArr(items).forEach(key => {
  329. res[key] = cloneDeep(this.getState(key));
  330. });
  331. return res;
  332. }
  333. getTreeNodeProps(key: string) {
  334. const {
  335. expandedKeys = new Set([]),
  336. selectedKeys = [],
  337. checkedKeys = new Set([]),
  338. halfCheckedKeys = new Set([]),
  339. realCheckedKeys = new Set([]),
  340. keyEntities = {},
  341. filteredKeys = new Set([]),
  342. inputValue = '',
  343. loadedKeys = new Set([]),
  344. loadingKeys = new Set([]),
  345. filteredExpandedKeys = new Set([]),
  346. disabledKeys = new Set([]),
  347. } = this.getStates();
  348. const { treeNodeFilterProp, checkRelation } = this.getProps();
  349. const entity = keyEntities[key];
  350. const notExist = !entity;
  351. if (notExist) {
  352. return null;
  353. }
  354. // if checkRelation is invalid, the checked status of node will be false
  355. let realChecked = false;
  356. let realHalfChecked = false;
  357. if (checkRelation === 'related') {
  358. realChecked = checkedKeys.has(key);
  359. realHalfChecked = halfCheckedKeys.has(key);
  360. } else if (checkRelation === 'unRelated') {
  361. realChecked = realCheckedKeys.has(key);
  362. realHalfChecked = false;
  363. }
  364. const isSearching = Boolean(inputValue);
  365. const treeNodeProps: BasicTreeNodeProps = {
  366. eventKey: key,
  367. expanded: isSearching ? filteredExpandedKeys.has(key) : expandedKeys.has(key),
  368. selected: selectedKeys.includes(key),
  369. checked: realChecked,
  370. halfChecked: realHalfChecked,
  371. pos: String(entity ? entity.pos : ''),
  372. level: entity.level,
  373. filtered: filteredKeys.has(key),
  374. loading: loadingKeys.has(key) && !loadedKeys.has(key),
  375. loaded: loadedKeys.has(key),
  376. keyword: inputValue,
  377. treeNodeFilterProp,
  378. };
  379. if (this.getProp('disableStrictly') && disabledKeys.has(key)) {
  380. treeNodeProps.disabled = true;
  381. }
  382. return treeNodeProps;
  383. }
  384. notifyJsonChange(key: string[] | string, e: any) {
  385. const data = this.getProp('treeDataSimpleJson');
  386. const selectedPath = normalizedArr(key).map(i => i.replace('-', '.'));
  387. const value = pick(data, selectedPath);
  388. this._adapter.notifyChange(value as BasicValue);
  389. }
  390. notifyMultipleChange(key: string[], e: any) {
  391. const { keyEntities } = this.getStates();
  392. const { leafOnly, checkRelation } = this.getProps();
  393. let value;
  394. let keyList = [];
  395. if (checkRelation === 'related') {
  396. keyList = normalizeKeyList(key, keyEntities, leafOnly);
  397. } else if (checkRelation === 'unRelated') {
  398. keyList = key;
  399. }
  400. if (this.getProp('onChangeWithObject')) {
  401. value = keyList.map((itemKey: string) => keyEntities[itemKey].data);
  402. } else {
  403. value = getValueOrKey(keyList.map((itemKey: string) => keyEntities[itemKey].data));
  404. }
  405. this._adapter.notifyChange(value);
  406. }
  407. notifyChange(key: string[] | string, e: any) {
  408. const isMultiple = this._isMultiple();
  409. const { keyEntities } = this.getStates();
  410. if (this.getProp('treeDataSimpleJson')) {
  411. this.notifyJsonChange(key, e);
  412. } else if (isMultiple) {
  413. this.notifyMultipleChange(key as string[], e);
  414. } else {
  415. let value;
  416. if (this.getProp('onChangeWithObject')) {
  417. value = get(keyEntities, key).data;
  418. } else {
  419. const { data } = get(keyEntities, key);
  420. value = getValueOrKey(data);
  421. }
  422. this._adapter.notifyChange(value);
  423. }
  424. }
  425. handleInputChange(sugInput: string) {
  426. // Input is a controlled component, so the value value needs to be updated
  427. this._adapter.updateInputValue(sugInput);
  428. const { expandedKeys, selectedKeys, keyEntities, treeData } = this.getStates();
  429. const { showFilteredOnly, filterTreeNode, treeNodeFilterProp } = this.getProps();
  430. let filteredOptsKeys: string[] = [];
  431. let expandedOptsKeys: string[] = [];
  432. let flattenNodes: BasicFlattenNode[] = [];
  433. let filteredShownKeys = new Set([]);
  434. if (!sugInput) {
  435. expandedOptsKeys = findAncestorKeys(selectedKeys, keyEntities);
  436. expandedOptsKeys.forEach(item => expandedKeys.add(item));
  437. flattenNodes = flattenTreeData(treeData, expandedKeys);
  438. } else {
  439. filteredOptsKeys = Object.values(keyEntities)
  440. .filter((item: BasicKeyEntity) => filter(sugInput, item.data, filterTreeNode, treeNodeFilterProp))
  441. .map((item: BasicKeyEntity) => item.key);
  442. expandedOptsKeys = findAncestorKeys(filteredOptsKeys, keyEntities, false);
  443. const shownChildKeys = findDescendantKeys(filteredOptsKeys, keyEntities, true);
  444. filteredShownKeys = new Set([...shownChildKeys, ...expandedOptsKeys]);
  445. flattenNodes = flattenTreeData(
  446. treeData,
  447. new Set(expandedOptsKeys),
  448. showFilteredOnly && filteredShownKeys
  449. );
  450. }
  451. const newFilteredExpandedKeys = new Set(expandedOptsKeys);
  452. this._adapter.notifySearch(sugInput, Array.from(newFilteredExpandedKeys));
  453. this._adapter.updateState({
  454. expandedKeys,
  455. flattenNodes,
  456. motionKeys: new Set([]),
  457. filteredKeys: new Set(filteredOptsKeys),
  458. filteredExpandedKeys: newFilteredExpandedKeys,
  459. filteredShownKeys,
  460. });
  461. }
  462. handleNodeSelect(e: any, treeNode: BasicTreeNodeProps) {
  463. const isDisabled = this._isDisabled(treeNode);
  464. if (isDisabled) {
  465. return;
  466. }
  467. if (!this._isMultiple()) {
  468. this.handleSingleSelect(e, treeNode);
  469. } else {
  470. this.handleMultipleSelect(e, treeNode);
  471. }
  472. }
  473. handleNodeRightClick(e: any, treeNode: BasicTreeNodeProps) {
  474. this._adapter.notifyRightClick(e, treeNode.data);
  475. }
  476. handleNodeDoubleClick(e: any, treeNode: BasicTreeNodeProps) {
  477. this._adapter.notifyDoubleClick(e, treeNode.data);
  478. }
  479. handleSingleSelect(e: any, treeNode: BasicTreeNodeProps) {
  480. let { selectedKeys } = this.getCopyFromState('selectedKeys');
  481. const { selected, eventKey, data } = treeNode;
  482. const targetSelected = !selected;
  483. this._adapter.notifySelect(eventKey, true, data);
  484. if (!targetSelected) {
  485. return;
  486. }
  487. if (!selectedKeys.includes(eventKey)) {
  488. selectedKeys = [eventKey];
  489. this.notifyChange(eventKey, e);
  490. if (!this._isControlledComponent()) {
  491. this._adapter.updateState({ selectedKeys });
  492. }
  493. }
  494. }
  495. calcCheckedKeys(eventKey: string, targetStatus: boolean) {
  496. const { keyEntities } = this.getStates();
  497. const { checkedKeys, halfCheckedKeys } = this.getCopyFromState(['checkedKeys', 'halfCheckedKeys']);
  498. return targetStatus ?
  499. calcCheckedKeysForChecked(eventKey, keyEntities, checkedKeys, halfCheckedKeys) :
  500. calcCheckedKeysForUnchecked(eventKey, keyEntities, checkedKeys, halfCheckedKeys);
  501. }
  502. /*
  503. * Compute the checked state of the node
  504. */
  505. calcCheckedStatus(targetStatus: boolean, eventKey: string) {
  506. // From checked to unchecked, you can change it directly
  507. if (!targetStatus) {
  508. return targetStatus;
  509. }
  510. // Starting from unchecked, you need to judge according to the descendant nodes
  511. const { checkedKeys, keyEntities, disabledKeys } = this.getStates();
  512. const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
  513. const hasDisabled = descendantKeys.some((key: string) => disabledKeys.has(key));
  514. // If the descendant nodes are not disabled, they will be directly changed to checked
  515. if (!hasDisabled) {
  516. return targetStatus;
  517. }
  518. // If all descendant nodes that are not disabled are selected, return unchecked, otherwise, return checked
  519. const nonDisabledKeys = descendantKeys.filter((key: string) => !disabledKeys.has(key));
  520. const allChecked = nonDisabledKeys.every((key: string) => checkedKeys.has(key));
  521. return !allChecked;
  522. }
  523. /*
  524. * In strict disable mode, calculate the nodes of checked and halfCheckedKeys and return their corresponding keys
  525. */
  526. calcNonDisabledCheckedKeys(eventKey: string, targetStatus: boolean) {
  527. const { keyEntities, disabledKeys } = this.getStates();
  528. const { checkedKeys } = this.getCopyFromState(['checkedKeys']);
  529. const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
  530. const hasDisabled = descendantKeys.some((key: string) => disabledKeys.has(key));
  531. // If none of the descendant nodes are disabled, follow the normal logic
  532. if (!hasDisabled) {
  533. return this.calcCheckedKeys(eventKey, targetStatus);
  534. }
  535. const nonDisabled = descendantKeys.filter((key: string) => !disabledKeys.has(key));
  536. const newCheckedKeys = targetStatus ?
  537. [...nonDisabled, ...checkedKeys] :
  538. difference(normalizeKeyList([...checkedKeys], keyEntities, true), nonDisabled);
  539. return calcCheckedKeys(newCheckedKeys, keyEntities);
  540. }
  541. /*
  542. * Handle the selection event in the case of multiple selection
  543. */
  544. handleMultipleSelect(e: any, treeNode: BasicTreeNodeProps) {
  545. const { disableStrictly, checkRelation } = this.getProps();
  546. const { realCheckedKeys } = this.getStates();
  547. // eventKey: The key value of the currently clicked node
  548. const { checked, eventKey, data } = treeNode;
  549. if (checkRelation === 'related') {
  550. // Find the checked state of the current node
  551. const targetStatus = disableStrictly ? this.calcCheckedStatus(!checked, eventKey) : !checked;
  552. const { checkedKeys, halfCheckedKeys } = disableStrictly ?
  553. this.calcNonDisabledCheckedKeys(eventKey, targetStatus) :
  554. this.calcCheckedKeys(eventKey, targetStatus);
  555. this._adapter.notifySelect(eventKey, targetStatus, data);
  556. this.notifyChange([...checkedKeys], e);
  557. if (!this._isControlledComponent()) {
  558. this._adapter.updateState({ checkedKeys, halfCheckedKeys });
  559. }
  560. } else if (checkRelation === 'unRelated') {
  561. const newRealCheckedKeys: Set<string> = new Set(realCheckedKeys);
  562. let targetStatus: boolean;
  563. if (realCheckedKeys.has(eventKey)) {
  564. newRealCheckedKeys.delete(eventKey);
  565. targetStatus = false;
  566. } else {
  567. newRealCheckedKeys.add(eventKey);
  568. targetStatus = true;
  569. }
  570. this._adapter.notifySelect(eventKey, targetStatus, data);
  571. this.notifyChange([...newRealCheckedKeys], e);
  572. if (!this._isControlledComponent()) {
  573. this._adapter.updateState({ realCheckedKeys: newRealCheckedKeys });
  574. }
  575. }
  576. }
  577. setExpandedStatus(treeNode: BasicTreeNodeProps) {
  578. const { inputValue, treeData, filteredShownKeys, keyEntities } = this.getStates();
  579. const isSearching = Boolean(inputValue);
  580. const showFilteredOnly = this._showFilteredOnly();
  581. const expandedStateKey = isSearching ? 'filteredExpandedKeys' : 'expandedKeys';
  582. const expandedKeys = this.getCopyFromState(expandedStateKey)[expandedStateKey];
  583. let motionType = 'show';
  584. const { eventKey, expanded, data } = treeNode;
  585. if (!expanded) {
  586. expandedKeys.add(eventKey);
  587. } else if (expandedKeys.has(eventKey)) {
  588. expandedKeys.delete(eventKey);
  589. motionType = 'hide';
  590. }
  591. this._adapter.cacheFlattenNodes(motionType === 'hide' && this._isAnimated());
  592. if (!this._isExpandControlled()) {
  593. const flattenNodes = flattenTreeData(
  594. treeData,
  595. expandedKeys,
  596. isSearching && showFilteredOnly && filteredShownKeys
  597. );
  598. const motionKeys = this._isAnimated() ? getMotionKeys(eventKey, expandedKeys, keyEntities) : [];
  599. const newState = {
  600. [expandedStateKey]: expandedKeys,
  601. flattenNodes,
  602. motionKeys: new Set(motionKeys),
  603. motionType,
  604. };
  605. this._adapter.updateState(newState);
  606. }
  607. return {
  608. expandedKeys,
  609. expanded: !expanded,
  610. data,
  611. };
  612. }
  613. handleNodeExpand(e: any, treeNode: BasicTreeNodeProps) {
  614. const { loadData } = this.getProps();
  615. if (!loadData && (!treeNode.children || !treeNode.children.length)) {
  616. return;
  617. }
  618. const { expandedKeys, data, expanded } = this.setExpandedStatus(treeNode);
  619. this._adapter.notifyExpand(expandedKeys, {
  620. expanded,
  621. node: data,
  622. });
  623. }
  624. handleNodeLoad(loadedKeys: Set<string>, loadingKeys: Set<string>, data: BasicTreeNodeData, resolve: (value?: any) => void) {
  625. const { loadData, onLoad } = this.getProps();
  626. const { key } = data;
  627. if (!loadData || loadedKeys.has(key) || loadingKeys.has(key)) {
  628. return {};
  629. }
  630. // Process the loaded data
  631. loadData(data).then(() => {
  632. const {
  633. loadedKeys: prevLoadedKeys,
  634. loadingKeys: prevLoadingKeys
  635. } = this.getCopyFromState(['loadedKeys', 'loadingKeys']);
  636. const newLoadedKeys = prevLoadedKeys.add(key);
  637. const newLoadingKeys = new Set([...prevLoadingKeys]);
  638. newLoadingKeys.delete(key);
  639. // onLoad should be triggered before internal setState to avoid `loadData` being triggered twice
  640. onLoad && onLoad(newLoadedKeys, data);
  641. if (!this._isLoadControlled()) {
  642. this._adapter.updateState({
  643. loadedKeys: newLoadedKeys,
  644. });
  645. }
  646. this._adapter.setState({
  647. loadingKeys: newLoadingKeys,
  648. } as any);
  649. resolve();
  650. });
  651. return {
  652. loadingKeys: loadingKeys.add(key),
  653. };
  654. }
  655. // Drag and drop related processing logic
  656. getDragEventNodeData(node: BasicTreeNodeData) {
  657. return {
  658. ...node.data,
  659. ...pick(node, ['expanded', 'pos', 'children']),
  660. };
  661. }
  662. triggerDragEvent(name: string, event: any, node: BasicTreeNodeData, extra = {}) {
  663. const callEvent = this.getProp(name);
  664. callEvent &&
  665. callEvent({
  666. event,
  667. node: this.getDragEventNodeData(node),
  668. ...extra,
  669. });
  670. }
  671. clearDragState = () => {
  672. this._adapter.updateState({
  673. dragOverNodeKey: '',
  674. dragging: false,
  675. });
  676. };
  677. handleNodeDragStart(e: any, treeNode: BasicTreeNodeData) {
  678. const { keyEntities } = this.getStates();
  679. const { hideDraggingNode, renderDraggingNode } = this.getProps();
  680. const { eventKey, nodeInstance, data } = treeNode;
  681. if (hideDraggingNode || renderDraggingNode) {
  682. let dragImg;
  683. if (typeof renderDraggingNode === 'function') {
  684. dragImg = renderDraggingNode(nodeInstance, data);
  685. } else if (hideDraggingNode) {
  686. dragImg = nodeInstance.cloneNode(true);
  687. dragImg.style.opacity = 0;
  688. }
  689. document.body.appendChild(dragImg);
  690. e.dataTransfer.setDragImage(dragImg, 0, 0);
  691. }
  692. this._adapter.setDragNode(treeNode);
  693. this._adapter.updateState({
  694. dragging: true,
  695. dragNodesKeys: new Set(getDragNodesKeys(eventKey, keyEntities)),
  696. });
  697. this.triggerDragEvent('onDragStart', e, treeNode);
  698. }
  699. handleNodeDragEnter(e: any, treeNode: BasicTreeNodeData, dragNode: any) {
  700. const { dragging, dragNodesKeys } = this.getStates();
  701. const { autoExpandWhenDragEnter } = this.getProps();
  702. const { pos, eventKey, expanded } = treeNode;
  703. if (!dragNode || dragNodesKeys.has(eventKey)) {
  704. return;
  705. }
  706. const dropPosition = calcDropRelativePosition(e, treeNode);
  707. // If the drag node is itself, skip
  708. if (dragNode.eventKey === eventKey && dropPosition === 0) {
  709. this._adapter.updateState({
  710. dragOverNodeKey: '',
  711. dropPosition: null,
  712. });
  713. return;
  714. }
  715. // Trigger dragenter after clearing the prev state in dragleave
  716. setTimeout(() => {
  717. this._adapter.updateState({
  718. dragOverNodeKey: eventKey,
  719. dropPosition,
  720. });
  721. // If autoExpand is already expanded or not allowed, trigger the event and return
  722. if (!autoExpandWhenDragEnter || expanded) {
  723. this.triggerDragEvent('onDragEnter', e, treeNode);
  724. return;
  725. }
  726. // Side effects of delayed drag
  727. if (!this.delayedDragEnterLogic) {
  728. this.delayedDragEnterLogic = {};
  729. }
  730. Object.keys(this.delayedDragEnterLogic).forEach(key => {
  731. clearTimeout(this.delayedDragEnterLogic[key]);
  732. });
  733. this.delayedDragEnterLogic[pos] = window.setTimeout(() => {
  734. if (!dragging) {
  735. return;
  736. }
  737. const { expandedKeys: newExpandedKeys } = this.setExpandedStatus(treeNode);
  738. this.triggerDragEvent('onDragEnter', e, treeNode, { expandedKeys: [...newExpandedKeys] });
  739. }, 400);
  740. }, 0);
  741. }
  742. handleNodeDragOver(e: any, treeNode: BasicTreeNodeData, dragNode: any) {
  743. const { dropPosition, dragNodesKeys, dragOverNodeKey } = this.getStates();
  744. const { eventKey } = treeNode;
  745. if (dragNodesKeys.has(eventKey)) {
  746. return;
  747. }
  748. // Update the drag position
  749. if (dragNode && eventKey === dragOverNodeKey) {
  750. const newPos = calcDropRelativePosition(e, treeNode);
  751. if (dropPosition === newPos) {
  752. return;
  753. }
  754. this._adapter.updateState({
  755. dropPosition: newPos,
  756. });
  757. }
  758. this.triggerDragEvent('onDragOver', e, treeNode);
  759. }
  760. handleNodeDragLeave(e: any, treeNode: BasicTreeNodeData) {
  761. this._adapter.updateState({
  762. dragOverNodeKey: '',
  763. });
  764. this.triggerDragEvent('onDragLeave', e, treeNode);
  765. }
  766. handleNodeDragEnd(e: any, treeNode: BasicTreeNodeData) {
  767. this.clearDragState();
  768. this.triggerDragEvent('onDragEnd', e, treeNode);
  769. this._adapter.setDragNode(null);
  770. }
  771. handleNodeDrop(e: any, treeNode: BasicTreeNodeData, dragNode: any) {
  772. const { dropPosition, dragNodesKeys } = this.getStates();
  773. const { eventKey, pos } = treeNode;
  774. this.clearDragState();
  775. if (dragNodesKeys.has(eventKey)) {
  776. return;
  777. }
  778. const dropRes = {
  779. dragNode: dragNode ? this.getDragEventNodeData(dragNode) : null,
  780. dragNodesKeys: [...dragNodesKeys],
  781. dropPosition: calcDropActualPosition(pos, dropPosition),
  782. dropToGap: dropPosition !== 0,
  783. };
  784. this.triggerDragEvent('onDrop', e, treeNode, dropRes);
  785. this._adapter.setDragNode(null);
  786. }
  787. }