foundation.ts 26 KB

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