foundation.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. import { isNumber, isFunction, get, isUndefined, isString, cloneDeep, isEmpty, difference } from 'lodash-es';
  2. import { strings } from '../treeSelect/constants';
  3. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  4. import {
  5. flattenTreeData,
  6. findDescendantKeys,
  7. findAncestorKeys,
  8. filter,
  9. normalizedArr,
  10. normalizeKeyList,
  11. getMotionKeys,
  12. calcCheckedKeysForChecked,
  13. calcCheckedKeys,
  14. calcCheckedKeysForUnchecked,
  15. getValueOrKey
  16. } from '../tree/treeUtil';
  17. import {
  18. BasicTreeInnerData,
  19. BasicTreeProps,
  20. BasicTreeNodeData,
  21. BasicTreeNodeProps,
  22. BasicKeyEntity,
  23. BasicExpandedOtherProps
  24. } from '../tree/foundation';
  25. import { Motion } from '../utils/type';
  26. /* Here ValidateStatus is the same as ValidateStatus in baseComponent */
  27. export type ValidateStatus = 'error' | 'warning' | 'default';
  28. export type Size = 'small' | 'large' | 'default';
  29. export interface BasicRenderSelectedItem {
  30. (treeNode: BasicTreeNodeData): any;
  31. (treeNode: BasicTreeNodeData, otherProps: { index: number | string; onClose: (tagContent: any, e: any) => void }):
  32. {
  33. isRenderInTag: boolean;
  34. content: any;
  35. };
  36. }
  37. export interface BasicTriggerRenderProps {
  38. [x: string]: any;
  39. componentProps: BasicTreeSelectProps;
  40. disabled: boolean;
  41. inputValue: string;
  42. placeholder: string;
  43. value: BasicTreeNodeData[];
  44. onClear: (e: any) => void;
  45. }
  46. export type BasicOnChangeWithObject = (node: BasicTreeNodeData[] | BasicTreeNodeData, e: any) => void;
  47. export type BasicOnChangeWithBasic = (
  48. value: BasicTreeNodeData['value'],
  49. node: BasicTreeNodeData[] | BasicTreeNodeData,
  50. e: any
  51. ) => void;
  52. export interface BasicOnChange {
  53. (node: BasicTreeNodeData[] | BasicTreeNodeData, e: any): void;
  54. (
  55. value: BasicTreeNodeData['value'] | Array<BasicTreeNodeData['value']>,
  56. node: BasicTreeNodeData[] | BasicTreeNodeData,
  57. e: any
  58. ): void;
  59. }
  60. export interface BasicTreeSelectProps extends Pick<BasicTreeProps,
  61. 'virtualize'
  62. | 'renderFullLabel'
  63. | 'renderLabel'
  64. | 'autoExpandParent'
  65. | 'className'
  66. | 'defaultExpandAll'
  67. | 'defaultExpandedKeys'
  68. | 'defaultValue'
  69. | 'disabled'
  70. | 'emptyContent'
  71. | 'expandAction'
  72. | 'expandedKeys'
  73. | 'filterTreeNode'
  74. | 'labelEllipsis'
  75. | 'leafOnly'
  76. | 'multiple'
  77. | 'onChangeWithObject'
  78. | 'showClear'
  79. | 'showFilteredOnly'
  80. | 'style'
  81. | 'treeData'
  82. | 'treeNodeFilterProp'
  83. | 'value'
  84. | 'onExpand'
  85. | 'onSearch'
  86. | 'expandAll'
  87. | 'disableStrictly'
  88. > {
  89. motion?: Motion;
  90. mouseEnterDelay?: number;
  91. mouseLeaveDelay?: number;
  92. arrowIcon?: any;
  93. autoAdjustOverflow?: boolean;
  94. clickToHide?: boolean;
  95. defaultOpen?: boolean;
  96. dropdownClassName?: string;
  97. dropdownMatchSelectWidth?: boolean;
  98. dropdownStyle?: any;
  99. insetLabel?: any;
  100. maxTagCount?: number;
  101. motionExpand?: boolean;
  102. optionListStyle?: any;
  103. outerBottomSlot?: any;
  104. outerTopSlot?: any;
  105. placeholder?: string;
  106. prefix?: any;
  107. searchAutoFocus?: boolean;
  108. searchPlaceholder?: string;
  109. showSearchClear?: boolean;
  110. size?: Size;
  111. suffix?: any;
  112. treeNodeLabelProp?: string;
  113. validateStatus?: ValidateStatus;
  114. zIndex?: number;
  115. searchPosition?: string;
  116. stopPropagation?: boolean | string;
  117. loadedKeys?: string[];
  118. loadData?: (data: BasicTreeNodeData) => Promise<void>;
  119. onSelect?: (selectedKeys: string, selected: boolean, selectedNode: BasicTreeNodeData) => void;
  120. searchRender?: (inputProps: any) => any;
  121. renderSelectedItem?: BasicRenderSelectedItem;
  122. getPopupContainer?: () => HTMLElement;
  123. triggerRender?: (props: BasicTriggerRenderProps) => any;
  124. onBlur?: (e: any) => void;
  125. onChange?: BasicOnChange;
  126. onFocus?: (e: any) => void;
  127. onVisibleChange?: (isVisible: boolean) => void;
  128. onLoad?: (keys: Set<string>, data: BasicTreeNodeData) => void;
  129. }
  130. export interface BasicTreeSelectInnerData extends Pick<BasicTreeInnerData,
  131. 'keyEntities'
  132. | 'treeData'
  133. | 'flattenNodes'
  134. | 'selectedKeys'
  135. | 'checkedKeys'
  136. | 'halfCheckedKeys'
  137. | 'motionKeys'
  138. | 'motionType'
  139. | 'expandedKeys'
  140. | 'filteredKeys'
  141. | 'filteredExpandedKeys'
  142. | 'filteredShownKeys'
  143. | 'cachedKeyValuePairs'
  144. | 'inputValue'
  145. | 'disabledKeys'
  146. | 'loadedKeys'
  147. | 'loadingKeys'
  148. > {
  149. inputTriggerFocus: boolean;
  150. isOpen: boolean;
  151. isInput: boolean;
  152. rePosKey: number;
  153. dropdownMinWidth: null | number;
  154. isHovering: boolean;
  155. prevProps: BasicTreeSelectProps;
  156. }
  157. export interface TreeSelectAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
  158. updateInputValue: (value: string) => void;
  159. registerClickOutsideHandler: (cb: (e: any) => void) => void;
  160. unregisterClickOutsideHandler: () => void;
  161. rePositionDropdown: () => void;
  162. updateState: (states: Pick<S, keyof S>) => void;
  163. notifySelect: (selectedKeys: string, selected: boolean, selectedNode: BasicTreeNodeData) => void;
  164. notifySearch: (input: string) => void;
  165. cacheFlattenNodes: (bool: boolean) => void;
  166. openMenu: () => void;
  167. closeMenu: (cb?: () => void) => void;
  168. getTriggerWidth: () => boolean | number;
  169. setOptionWrapperWidth: (width: null | number) => void;
  170. notifyChange: BasicOnChangeWithBasic;
  171. notifyChangeWithObject: BasicOnChangeWithObject;
  172. notifyExpand: (expandedKeys: Set<string>, expanedOtherProps: BasicExpandedOtherProps) => void;
  173. notifyFocus: (e: any) => void;
  174. notifyBlur: (e: any) => void;
  175. toggleHovering: (bool: boolean) => void;
  176. notifyLoad: (newLoadedKeys: Set<string>, data: BasicTreeNodeData) => void;
  177. updateInputFocus: (bool: boolean) => void;
  178. updateLoadKeys: (data: BasicTreeNodeData, resolve: (value?: any) => void) => void;
  179. }
  180. // eslint-disable-next-line max-len
  181. export default class TreeSelectFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<TreeSelectAdapter<P, S>, P, S> {
  182. constructor(adapter: TreeSelectAdapter<P, S>) {
  183. super({ ...adapter });
  184. }
  185. init() {
  186. const { searchAutoFocus, searchPosition, filterTreeNode } = this.getProps();
  187. const triggerSearch = searchPosition === strings.SEARCH_POSITION_TRIGGER && filterTreeNode;
  188. const triggerSearchAutoFocus = searchAutoFocus && triggerSearch;
  189. this._setDropdownWidth();
  190. const isOpen = (this.getProp('defaultOpen') || triggerSearchAutoFocus) && !this._isDisabled();
  191. if (isOpen) {
  192. this.open();
  193. }
  194. }
  195. destroy() {
  196. // Ensure that event monitoring will be uninstalled, and the user may not trigger closePanel
  197. this._adapter.unregisterClickOutsideHandler();
  198. }
  199. _setDropdownWidth() {
  200. const { style, dropdownMatchSelectWidth } = this.getProps();
  201. let width;
  202. if (dropdownMatchSelectWidth) {
  203. if (style && isNumber(style.width)) {
  204. width = style.width;
  205. } else if (style && isString(style.width) && !style.width.includes('%')) {
  206. width = style.width;
  207. } else {
  208. width = this._adapter.getTriggerWidth();
  209. }
  210. this._adapter.setOptionWrapperWidth(width);
  211. }
  212. }
  213. _isMultiple() {
  214. return this.getProp('multiple');
  215. }
  216. _isAnimated() {
  217. return this.getProp('motionExpand');
  218. }
  219. _isDisabled(treeNode = {} as BasicTreeNodeProps) {
  220. return this.getProp('disabled') || treeNode.disabled;
  221. }
  222. _isExpandControlled() {
  223. return this.getProp('expandedKeys');
  224. }
  225. _isSelectToClose() {
  226. return !this.getProp('expandAction');
  227. }
  228. _isLoadControlled() {
  229. return this.getProp('loadedKeys');
  230. }
  231. _showFilteredOnly() {
  232. const { inputValue } = this.getStates();
  233. const { showFilteredOnly } = this.getProps();
  234. return Boolean(inputValue) && showFilteredOnly;
  235. }
  236. getCopyFromState(items: string | string[]) {
  237. const res = {};
  238. normalizedArr(items).forEach(key => {
  239. res[key] = cloneDeep(this.getState(key));
  240. });
  241. return res as BasicTreeInnerData;
  242. }
  243. getTreeNodeProps(key: string) {
  244. const {
  245. expandedKeys = new Set([]),
  246. selectedKeys = [],
  247. checkedKeys = new Set([]),
  248. halfCheckedKeys = new Set([]),
  249. keyEntities = {},
  250. filteredKeys = new Set([]),
  251. inputValue = '',
  252. loadedKeys,
  253. loadingKeys,
  254. filteredExpandedKeys = new Set([]),
  255. disabledKeys = new Set([]),
  256. } = this.getStates();
  257. const { treeNodeFilterProp } = this.getProps();
  258. const entity = keyEntities[key];
  259. const notExist = !entity;
  260. if (notExist) {
  261. return null;
  262. }
  263. const isSearching = Boolean(inputValue);
  264. const treeNodeProps: BasicTreeNodeProps = {
  265. eventKey: key,
  266. expanded: isSearching ? filteredExpandedKeys.has(key) : expandedKeys.has(key),
  267. selected: selectedKeys.includes(key),
  268. checked: checkedKeys.has(key),
  269. halfChecked: halfCheckedKeys.has(key),
  270. pos: String(entity ? entity.pos : ''),
  271. level: entity.level,
  272. filtered: filteredKeys.has(key),
  273. keyword: inputValue,
  274. treeNodeFilterProp,
  275. loading: loadingKeys.has(key) && !loadedKeys.has(key),
  276. loaded: loadedKeys.has(key),
  277. };
  278. if (this.getProp('disableStrictly') && disabledKeys.has(key)) {
  279. treeNodeProps.disabled = true;
  280. }
  281. return treeNodeProps;
  282. }
  283. handleNodeLoad(loadedKeys: Set<string>, loadingKeys: Set<string>, data: BasicTreeNodeData, resolve: (value?: any) => void) {
  284. const { loadData } = this.getProps();
  285. const { key } = data;
  286. if (!loadData || loadedKeys.has(key) || loadingKeys.has(key)) {
  287. return {};
  288. }
  289. loadData(data).then(() => {
  290. const {
  291. loadedKeys: prevLoadedKeys,
  292. loadingKeys: prevLoadingKeys
  293. } = this.getCopyFromState(['loadedKeys', 'loadingKeys']);
  294. const newLoadedKeys: Set<string> = prevLoadedKeys.add(key);
  295. const newLoadingKeys: Set<string> = new Set([...prevLoadingKeys]);
  296. newLoadingKeys.delete(key);
  297. this._adapter.notifyLoad(newLoadedKeys, data);
  298. if (!this._isLoadControlled()) {
  299. this._adapter.updateState({
  300. loadedKeys: newLoadedKeys,
  301. } as any);
  302. }
  303. this._adapter.setState({
  304. loadingKeys: newLoadingKeys,
  305. } as any);
  306. resolve();
  307. });
  308. return {
  309. loadingKeys: loadingKeys.add(key),
  310. };
  311. }
  312. focusInput(bool: boolean) {
  313. this._adapter.updateInputFocus(bool);
  314. }
  315. _notifyMultipleChange(key: string[], e: any) {
  316. const { keyEntities } = this.getStates();
  317. const { leafOnly } = this.getProps();
  318. const keyList = normalizeKeyList(key, keyEntities, leafOnly);
  319. const nodes = keyList.map(i => keyEntities[i].data);
  320. if (this.getProp('onChangeWithObject')) {
  321. this._adapter.notifyChangeWithObject(nodes, e);
  322. } else {
  323. const value = getValueOrKey(nodes);
  324. this._adapter.notifyChange(value, nodes, e);
  325. }
  326. }
  327. _notifyChange(key: any, e: any) {
  328. const { keyEntities } = this.getStates();
  329. if (this._isMultiple() && Array.isArray(key)) {
  330. this._notifyMultipleChange(key, e);
  331. } else {
  332. const nodes = isUndefined(key) ? key : keyEntities[key].data;
  333. const value = isUndefined(key) ? key : getValueOrKey(nodes);
  334. if (this.getProp('onChangeWithObject')) {
  335. this._adapter.notifyChangeWithObject(nodes, e);
  336. } else {
  337. this._adapter.notifyChange(value, nodes, e);
  338. }
  339. }
  340. }
  341. // Scenes that may trigger focus:
  342. // 1、click selection
  343. _notifyFocus(e: any) {
  344. this._adapter.notifyFocus(e);
  345. }
  346. // Scenes that may trigger blur
  347. // 1、clickOutSide
  348. // 2、click option / press enter, and then select complete(when multiple is false
  349. // 3、press esc when dropdown list open
  350. _notifyBlur(e: any) {
  351. this._adapter.notifyBlur(e);
  352. }
  353. toggleHoverState(bool: boolean) {
  354. this._adapter.toggleHovering(bool);
  355. }
  356. open() {
  357. this._adapter.openMenu();
  358. this._setDropdownWidth();
  359. this._adapter.registerClickOutsideHandler(e => {
  360. this.close(e);
  361. });
  362. }
  363. close(e: any) {
  364. this._adapter.closeMenu();
  365. this._adapter.unregisterClickOutsideHandler();
  366. this._notifyBlur(e);
  367. if (this.getProp('motionExpand')) {
  368. this._adapter.updateState({ motionKeys: new Set([]) } as any);
  369. }
  370. }
  371. handleClick(e: any) {
  372. const isDisabled = this._isDisabled();
  373. const { isOpen } = this.getStates();
  374. if (isDisabled) {
  375. return;
  376. } else if (!isOpen) {
  377. this.open();
  378. this._notifyFocus(e);
  379. } else if (isOpen) {
  380. this.close(e);
  381. }
  382. }
  383. handleClear(e: any) {
  384. const { searchPosition, filterTreeNode } = this.getProps();
  385. const { inputValue, selectedKeys } = this.getStates();
  386. const isMultiple = this._isMultiple();
  387. const isControlled = this._isControlledComponent();
  388. const value: string | string[] | undefined = isMultiple ? [] : undefined;
  389. this._notifyChange(value, e);
  390. if (!isControlled) {
  391. // reposition dropdown when selected values change
  392. this._adapter.rePositionDropdown();
  393. this._adapter.updateState({
  394. selectedKeys: [],
  395. checkedKeys: new Set(),
  396. halfCheckedKeys: new Set(),
  397. } as any);
  398. }
  399. // When triggerSearch, clicking the clear button will trigger to clear Input
  400. if (filterTreeNode && searchPosition === strings.SEARCH_POSITION_TRIGGER) {
  401. if (inputValue !== '') {
  402. if (isEmpty(selectedKeys)) {
  403. this.handleInputChange('');
  404. } else {
  405. this.clearInput();
  406. }
  407. }
  408. }
  409. }
  410. removeTag(eventKey: BasicTreeNodeData['key']) {
  411. const { disableStrictly } = this.getProps();
  412. const { keyEntities, disabledKeys } = this.getStates();
  413. const item = keyEntities[eventKey].data;
  414. if (item.disabled || (disableStrictly && disabledKeys.has(eventKey))) {
  415. return;
  416. }
  417. const { checkedKeys, halfCheckedKeys } = this.calcCheckedKeys(eventKey, false);
  418. this._notifyChange([...checkedKeys], null);
  419. if (!this._isControlledComponent()) {
  420. this._adapter.updateState({ checkedKeys, halfCheckedKeys } as any);
  421. this._adapter.rePositionDropdown();
  422. }
  423. this._adapter.notifySelect(eventKey, false, item);
  424. // reposition dropdown when selected values change
  425. this._adapter.rePositionDropdown();
  426. }
  427. clearInput() {
  428. const { expandedKeys, selectedKeys, keyEntities, treeData } = this.getStates();
  429. const expandedOptsKeys = findAncestorKeys(selectedKeys, keyEntities);
  430. expandedOptsKeys.forEach(item => expandedKeys.add(item));
  431. const flattenNodes = flattenTreeData(treeData, expandedKeys);
  432. this._adapter.updateState({
  433. expandedKeys,
  434. flattenNodes,
  435. inputValue: '',
  436. motionKeys: new Set([]),
  437. filteredKeys: new Set([]),
  438. filteredExpandedKeys: new Set(expandedOptsKeys),
  439. filteredShownKeys: new Set([])
  440. } as any);
  441. }
  442. handleInputChange(sugInput: string) {
  443. // Input is used as controlled component
  444. this._adapter.updateInputValue(sugInput);
  445. const { expandedKeys, selectedKeys, keyEntities, treeData } = this.getStates();
  446. const { showFilteredOnly, filterTreeNode, treeNodeFilterProp } = this.getProps();
  447. let filteredOptsKeys: string[] = [];
  448. let expandedOptsKeys = [];
  449. let flattenNodes = [];
  450. let filteredShownKeys = new Set([]);
  451. if (!sugInput) {
  452. expandedOptsKeys = findAncestorKeys(selectedKeys, keyEntities);
  453. expandedOptsKeys.forEach(item => expandedKeys.add(item));
  454. flattenNodes = flattenTreeData(treeData, expandedKeys);
  455. } else {
  456. filteredOptsKeys = Object.values(keyEntities)
  457. .filter((item: BasicKeyEntity) => {
  458. const { data } = item;
  459. return filter(sugInput, data, filterTreeNode, treeNodeFilterProp);
  460. })
  461. .map((item: BasicKeyEntity) => item.key);
  462. expandedOptsKeys = findAncestorKeys(filteredOptsKeys, keyEntities, false);
  463. const shownChildKeys = findDescendantKeys(filteredOptsKeys, keyEntities, true);
  464. filteredShownKeys = new Set([...shownChildKeys, ...expandedOptsKeys]);
  465. flattenNodes = flattenTreeData(treeData, new Set(expandedOptsKeys), showFilteredOnly && filteredShownKeys);
  466. }
  467. this._adapter.notifySearch(sugInput);
  468. this._adapter.updateState({
  469. expandedKeys,
  470. flattenNodes,
  471. motionKeys: new Set([]),
  472. filteredKeys: new Set(filteredOptsKeys),
  473. filteredExpandedKeys: new Set(expandedOptsKeys),
  474. filteredShownKeys,
  475. } as any);
  476. }
  477. handleNodeSelect(e: any, treeNode: BasicTreeNodeProps) {
  478. const isDisabled = this._isDisabled(treeNode);
  479. if (isDisabled) {
  480. return;
  481. }
  482. if (!this._isMultiple()) {
  483. this.handleSingleSelect(e, treeNode);
  484. } else {
  485. this.handleMultipleSelect(e, treeNode);
  486. }
  487. }
  488. handleSingleSelect(e: any, treeNode: BasicTreeNodeProps) {
  489. let { selectedKeys } = this.getCopyFromState('selectedKeys');
  490. const { clickToHide } = this.getProps();
  491. const { selected, eventKey, data } = treeNode;
  492. this._adapter.notifySelect(eventKey, true, data);
  493. if (!selectedKeys.includes(eventKey) && !selected) {
  494. selectedKeys = [eventKey];
  495. this._notifyChange(eventKey, e);
  496. if (!this._isControlledComponent()) {
  497. this._adapter.updateState({ selectedKeys } as any);
  498. }
  499. }
  500. if (clickToHide && (this._isSelectToClose() || !data.children)) {
  501. this.close(e);
  502. }
  503. }
  504. calcCheckedKeys(eventKey: BasicTreeNodeProps['eventKey'], targetStatus: boolean) {
  505. const { keyEntities } = this.getStates();
  506. const {
  507. checkedKeys,
  508. halfCheckedKeys
  509. } = this.getCopyFromState(['checkedKeys', 'halfCheckedKeys']);
  510. if (targetStatus) {
  511. return calcCheckedKeysForChecked(eventKey, keyEntities, checkedKeys, halfCheckedKeys);
  512. } else {
  513. return calcCheckedKeysForUnchecked(eventKey, keyEntities, checkedKeys, halfCheckedKeys);
  514. }
  515. }
  516. handleMultipleSelect(e: any, treeNode: BasicTreeNodeProps) {
  517. const { searchPosition, disableStrictly } = this.getProps();
  518. const { inputValue } = this.getStates();
  519. const { checked, eventKey, data } = treeNode;
  520. const targetStatus = disableStrictly ?
  521. this.calcChekcedStatus(!checked, eventKey) :
  522. !checked;
  523. const { checkedKeys, halfCheckedKeys } = disableStrictly ?
  524. this.calcNonDisabedCheckedKeys(eventKey, targetStatus) :
  525. this.calcCheckedKeys(eventKey, targetStatus);
  526. this._adapter.notifySelect(eventKey, targetStatus, data);
  527. this._notifyChange([...checkedKeys], e);
  528. if (searchPosition === strings.SEARCH_POSITION_TRIGGER && inputValue !== '') {
  529. this._adapter.updateState({ inputValue: '' } as any);
  530. }
  531. if (!this._isControlledComponent()) {
  532. this._adapter.updateState({ checkedKeys, halfCheckedKeys } as any);
  533. this._adapter.rePositionDropdown();
  534. }
  535. }
  536. calcNonDisabedCheckedKeys(eventKey: string, targetStatus: boolean) {
  537. const { keyEntities, disabledKeys } = this.getStates();
  538. const { checkedKeys } = this.getCopyFromState(['checkedKeys']);
  539. const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
  540. const hasDisabled = descendantKeys.some(key => disabledKeys.has(key));
  541. if (!hasDisabled) {
  542. return this.calcCheckedKeys(eventKey, targetStatus);
  543. }
  544. const nonDisabled = descendantKeys.filter(key => !disabledKeys.has(key));
  545. const newCheckedKeys = targetStatus ?
  546. [...nonDisabled, ...checkedKeys] :
  547. difference(normalizeKeyList([...checkedKeys], keyEntities, true), nonDisabled);
  548. return calcCheckedKeys(newCheckedKeys, keyEntities);
  549. }
  550. calcChekcedStatus(targetStatus: boolean, eventKey: string) {
  551. if (!targetStatus) {
  552. return targetStatus;
  553. }
  554. const { checkedKeys, keyEntities, disabledKeys } = this.getStates();
  555. const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
  556. const hasDisabled = descendantKeys.some(key => disabledKeys.has(key));
  557. if (!hasDisabled) {
  558. return targetStatus;
  559. }
  560. const nonDisabledKeys = descendantKeys.filter(key => !disabledKeys.has(key));
  561. const allChecked = nonDisabledKeys.every(key => checkedKeys.has(key));
  562. return !allChecked;
  563. }
  564. handleNodeExpandInSearch(e: any, treeNode: BasicTreeNodeProps) {
  565. const { treeData, filteredShownKeys, keyEntities } = this.getStates();
  566. const showFilteredOnly = this._showFilteredOnly();
  567. // clone otherwise will be modified unexpectedly
  568. const { filteredExpandedKeys } = this.getCopyFromState('filteredExpandedKeys');
  569. let motionType = 'show';
  570. const { eventKey, expanded, data } = treeNode;
  571. if (!expanded) {
  572. filteredExpandedKeys.add(eventKey);
  573. } else if (filteredExpandedKeys.has(eventKey)) {
  574. filteredExpandedKeys.delete(eventKey);
  575. motionType = 'hide';
  576. }
  577. // cache prev flattenNodes, otherwise the calculation will remove hidden items
  578. this._adapter.cacheFlattenNodes(motionType === 'hide' && this._isAnimated());
  579. if (!this._isExpandControlled()) {
  580. const flattenNodes = flattenTreeData(treeData, filteredExpandedKeys, showFilteredOnly && filteredShownKeys);
  581. const motionKeys = this._isAnimated() ? getMotionKeys(eventKey, filteredExpandedKeys, keyEntities) : [];
  582. const newState = {
  583. filteredExpandedKeys,
  584. flattenNodes,
  585. motionKeys: new Set(motionKeys),
  586. motionType,
  587. };
  588. this._adapter.updateState(newState as any);
  589. }
  590. this._adapter.notifyExpand(filteredExpandedKeys, {
  591. expanded: !expanded,
  592. node: data,
  593. });
  594. }
  595. handleNodeExpand(e: any, treeNode: BasicTreeNodeProps) {
  596. const { loadData } = this.getProps();
  597. const { inputValue, keyEntities } = this.getStates();
  598. const isSearching = Boolean(inputValue);
  599. if (!loadData && (!treeNode.children || !treeNode.children.length)) {
  600. return;
  601. }
  602. if (isSearching) {
  603. this.handleNodeExpandInSearch(e, treeNode);
  604. return;
  605. }
  606. const { treeData } = this.getStates();
  607. // clone otherwise will be modified unexpectedly
  608. const { expandedKeys } = this.getCopyFromState('expandedKeys');
  609. let motionType = 'show';
  610. const { eventKey, expanded, data } = treeNode;
  611. if (!expanded) {
  612. expandedKeys.add(eventKey);
  613. } else if (expandedKeys.has(eventKey)) {
  614. expandedKeys.delete(eventKey);
  615. motionType = 'hide';
  616. }
  617. this._adapter.cacheFlattenNodes(motionType === 'hide' && this._isAnimated());
  618. if (!this._isExpandControlled()) {
  619. const flattenNodes = flattenTreeData(treeData, expandedKeys);
  620. const motionKeys = this._isAnimated() ? getMotionKeys(eventKey, expandedKeys, keyEntities) : [];
  621. const newState = {
  622. expandedKeys,
  623. flattenNodes,
  624. motionKeys: new Set(motionKeys),
  625. motionType,
  626. };
  627. this._adapter.updateState(newState as any);
  628. }
  629. this._adapter.notifyExpand(expandedKeys, {
  630. expanded: !expanded,
  631. node: data,
  632. });
  633. }
  634. /**
  635. * The selected items that need to be displayed in the search box when obtaining a single selection
  636. */
  637. getRenderTextInSingle() {
  638. const { renderSelectedItem: propRenderSelectedItem, treeNodeLabelProp } = this.getProps();
  639. const { selectedKeys, keyEntities } = this.getStates();
  640. const renderSelectedItem = isFunction(propRenderSelectedItem) ?
  641. propRenderSelectedItem :
  642. (item: BasicTreeNodeData) => get(item, treeNodeLabelProp, null);
  643. const item = selectedKeys.length && keyEntities[selectedKeys[0]] ?
  644. keyEntities[selectedKeys[0]].data :
  645. undefined;
  646. const renderText = item && treeNodeLabelProp in item ? renderSelectedItem(item) : null;
  647. return renderText;
  648. }
  649. /**
  650. * When the search box is on the trigger, the blur event handling method
  651. */
  652. handleInputTriggerBlur() {
  653. this._adapter.updateState({
  654. inputTriggerFocus: false
  655. } as any);
  656. }
  657. /**
  658. * When the search box is on the trigger, the focus event processing method
  659. */
  660. handleInputTriggerFocus() {
  661. this.clearInput();
  662. this._adapter.updateState({
  663. inputTriggerFocus: true
  664. } as any);
  665. }
  666. setLoadKeys(data: BasicTreeNodeData, resolve: (value?: any) => void) {
  667. this._adapter.updateLoadKeys(data, resolve);
  668. }
  669. }