foundation.ts 31 KB

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