foundation.ts 26 KB

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