foundation.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  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. | 'expandAll'
  88. | 'disableStrictly'
  89. | 'aria-label'
  90. | 'checkRelation'
  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. showRestTagsPopover?: boolean;
  122. restTagsPopoverProps?: any;
  123. loadData?: (data: BasicTreeNodeData) => Promise<void>;
  124. onSelect?: (selectedKeys: string, selected: boolean, selectedNode: BasicTreeNodeData) => void;
  125. searchRender?: (inputProps: any) => any;
  126. renderSelectedItem?: BasicRenderSelectedItem;
  127. getPopupContainer?: () => HTMLElement;
  128. // triggerRender?: (props: BasicTriggerRenderProps) => any;
  129. onBlur?: (e: any) => void;
  130. onSearch?: (sunInput: string, filteredExpandedKeys: string[]) => void;
  131. onChange?: BasicOnChange;
  132. onFocus?: (e: any) => void;
  133. onVisibleChange?: (isVisible: boolean) => void;
  134. onLoad?: (keys: Set<string>, data: BasicTreeNodeData) => void
  135. }
  136. export interface BasicTreeSelectInnerData extends Pick<BasicTreeInnerData,
  137. 'keyEntities'
  138. | 'treeData'
  139. | 'flattenNodes'
  140. | 'selectedKeys'
  141. | 'checkedKeys'
  142. | 'halfCheckedKeys'
  143. | 'motionKeys'
  144. | 'motionType'
  145. | 'expandedKeys'
  146. | 'filteredKeys'
  147. | 'filteredExpandedKeys'
  148. | 'filteredShownKeys'
  149. | 'cachedKeyValuePairs'
  150. | 'inputValue'
  151. | 'disabledKeys'
  152. | 'loadedKeys'
  153. | 'loadingKeys'
  154. | 'realCheckedKeys'
  155. > {
  156. inputTriggerFocus: boolean;
  157. isOpen: boolean;
  158. isInput: boolean;
  159. rePosKey: number;
  160. dropdownMinWidth: null | number;
  161. isHovering: boolean;
  162. prevProps: BasicTreeSelectProps
  163. }
  164. export interface TreeSelectAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
  165. updateInputValue: (value: string) => void;
  166. registerClickOutsideHandler: (cb: (e: any) => void) => void;
  167. unregisterClickOutsideHandler: () => void;
  168. rePositionDropdown: () => void;
  169. updateState: (states: Partial<BasicTreeSelectInnerData>) => void;
  170. notifySelect: (selectedKeys: string, selected: boolean, selectedNode: BasicTreeNodeData) => void;
  171. notifySearch: (input: string, filteredExpandedKeys: string[]) => void;
  172. cacheFlattenNodes: (bool: boolean) => void;
  173. openMenu: () => void;
  174. closeMenu: (cb?: () => void) => void;
  175. getTriggerWidth: () => boolean | number;
  176. setOptionWrapperWidth: (width: null | number) => void;
  177. notifyChange: BasicOnChangeWithBasic;
  178. notifyChangeWithObject: BasicOnChangeWithObject;
  179. notifyExpand: (expandedKeys: Set<string>, expandedOtherProps: BasicExpandedOtherProps) => void;
  180. notifyFocus: (e: any) => void;
  181. notifyBlur: (e: any) => void;
  182. toggleHovering: (bool: boolean) => void;
  183. notifyLoad: (newLoadedKeys: Set<string>, data: BasicTreeNodeData) => void;
  184. updateInputFocus: (bool: boolean) => void;
  185. updateLoadKeys: (data: BasicTreeNodeData, resolve: (value?: any) => void) => void
  186. }
  187. // eslint-disable-next-line max-len
  188. export default class TreeSelectFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<TreeSelectAdapter<P, S>, P, S> {
  189. constructor(adapter: TreeSelectAdapter<P, S>) {
  190. super({ ...adapter });
  191. }
  192. init() {
  193. const { searchAutoFocus, searchPosition, filterTreeNode } = this.getProps();
  194. const triggerSearch = searchPosition === strings.SEARCH_POSITION_TRIGGER && filterTreeNode;
  195. const triggerSearchAutoFocus = searchAutoFocus && triggerSearch;
  196. this._setDropdownWidth();
  197. const isOpen = (this.getProp('defaultOpen') || triggerSearchAutoFocus) && !this._isDisabled();
  198. if (isOpen) {
  199. this.open();
  200. }
  201. }
  202. destroy() {
  203. // Ensure that event monitoring will be uninstalled, and the user may not trigger closePanel
  204. this._adapter.unregisterClickOutsideHandler();
  205. }
  206. _setDropdownWidth() {
  207. const { style, dropdownMatchSelectWidth } = this.getProps();
  208. let width;
  209. if (dropdownMatchSelectWidth) {
  210. if (style && isNumber(style.width)) {
  211. width = style.width;
  212. } else if (style && isString(style.width) && !style.width.includes('%')) {
  213. width = style.width;
  214. } else {
  215. width = this._adapter.getTriggerWidth();
  216. }
  217. this._adapter.setOptionWrapperWidth(width);
  218. }
  219. }
  220. _isMultiple() {
  221. return this.getProp('multiple');
  222. }
  223. _isAnimated() {
  224. return this.getProp('motionExpand');
  225. }
  226. _isDisabled(treeNode = {} as BasicTreeNodeProps) {
  227. return this.getProp('disabled') || treeNode.disabled;
  228. }
  229. _isExpandControlled() {
  230. return this.getProp('expandedKeys');
  231. }
  232. _isSelectToClose() {
  233. return !this.getProp('expandAction');
  234. }
  235. _isLoadControlled() {
  236. return this.getProp('loadedKeys');
  237. }
  238. _showFilteredOnly() {
  239. const { inputValue } = this.getStates();
  240. const { showFilteredOnly } = this.getProps();
  241. return Boolean(inputValue) && showFilteredOnly;
  242. }
  243. getCopyFromState(items: string | string[]) {
  244. const res = {};
  245. normalizedArr(items).forEach(key => {
  246. res[key] = cloneDeep(this.getState(key));
  247. });
  248. return res as BasicTreeInnerData;
  249. }
  250. getTreeNodeProps(key: string) {
  251. const {
  252. expandedKeys = new Set([]),
  253. selectedKeys = [],
  254. checkedKeys = new Set([]),
  255. halfCheckedKeys = new Set([]),
  256. realCheckedKeys = new Set([]),
  257. keyEntities = {},
  258. filteredKeys = new Set([]),
  259. inputValue = '',
  260. loadedKeys,
  261. loadingKeys,
  262. filteredExpandedKeys = new Set([]),
  263. disabledKeys = new Set([]),
  264. } = this.getStates();
  265. const { treeNodeFilterProp, checkRelation } = this.getProps();
  266. const entity = keyEntities[key];
  267. const notExist = !entity;
  268. if (notExist) {
  269. return null;
  270. }
  271. // if checkRelation is invalid, the checked status of node will be false
  272. let realChecked = false;
  273. let realHalfChecked = false;
  274. if (checkRelation === 'related') {
  275. realChecked = checkedKeys.has(key);
  276. realHalfChecked = halfCheckedKeys.has(key);
  277. } else if (checkRelation === 'unRelated') {
  278. realChecked = realCheckedKeys.has(key);
  279. realHalfChecked = false;
  280. }
  281. const isSearching = Boolean(inputValue);
  282. const treeNodeProps: BasicTreeNodeProps = {
  283. eventKey: key,
  284. expanded: isSearching && !this._isExpandControlled()
  285. ? filteredExpandedKeys.has(key)
  286. : expandedKeys.has(key),
  287. selected: selectedKeys.includes(key),
  288. checked: realChecked,
  289. halfChecked: realHalfChecked,
  290. pos: String(entity ? entity.pos : ''),
  291. level: entity.level,
  292. filtered: filteredKeys.has(key),
  293. keyword: inputValue,
  294. treeNodeFilterProp,
  295. loading: loadingKeys.has(key) && !loadedKeys.has(key),
  296. loaded: loadedKeys.has(key),
  297. };
  298. if (this.getProp('disableStrictly') && disabledKeys.has(key)) {
  299. treeNodeProps.disabled = true;
  300. }
  301. return treeNodeProps;
  302. }
  303. handleNodeLoad(loadedKeys: Set<string>, loadingKeys: Set<string>, data: BasicTreeNodeData, resolve: (value?: any) => void) {
  304. const { loadData } = this.getProps();
  305. const { key } = data;
  306. if (!loadData || loadedKeys.has(key) || loadingKeys.has(key)) {
  307. return {};
  308. }
  309. loadData(data).then(() => {
  310. const {
  311. loadedKeys: prevLoadedKeys,
  312. loadingKeys: prevLoadingKeys
  313. } = this.getCopyFromState(['loadedKeys', 'loadingKeys']);
  314. const newLoadedKeys: Set<string> = prevLoadedKeys.add(key);
  315. const newLoadingKeys: Set<string> = new Set([...prevLoadingKeys]);
  316. newLoadingKeys.delete(key);
  317. this._adapter.notifyLoad(newLoadedKeys, data);
  318. if (!this._isLoadControlled()) {
  319. this._adapter.updateState({
  320. loadedKeys: newLoadedKeys,
  321. });
  322. }
  323. this._adapter.setState({
  324. loadingKeys: newLoadingKeys,
  325. } as any);
  326. resolve();
  327. });
  328. return {
  329. loadingKeys: loadingKeys.add(key),
  330. };
  331. }
  332. /* istanbul ignore next */
  333. focusInput(bool: boolean) {
  334. this._adapter.updateInputFocus(bool);
  335. }
  336. _notifyMultipleChange(key: string[], e: any) {
  337. const { keyEntities } = this.getStates();
  338. const { leafOnly, checkRelation } = this.getProps();
  339. let keyList = [];
  340. if (checkRelation === 'related') {
  341. keyList = normalizeKeyList(key, keyEntities, leafOnly);
  342. } else if (checkRelation === 'unRelated') {
  343. keyList = key as string[];
  344. }
  345. const nodes = keyList.map(i => keyEntities[i].data);
  346. if (this.getProp('onChangeWithObject')) {
  347. this._adapter.notifyChangeWithObject(nodes, e);
  348. } else {
  349. const value = getValueOrKey(nodes);
  350. this._adapter.notifyChange(value, nodes, e);
  351. }
  352. }
  353. _notifyChange(key: any, e: any) {
  354. const { keyEntities } = this.getStates();
  355. if (this._isMultiple() && Array.isArray(key)) {
  356. this._notifyMultipleChange(key, e);
  357. } else {
  358. const nodes = isUndefined(key) ? key : keyEntities[key].data;
  359. const value = isUndefined(key) ? key : getValueOrKey(nodes);
  360. if (this.getProp('onChangeWithObject')) {
  361. this._adapter.notifyChangeWithObject(nodes, e);
  362. } else {
  363. this._adapter.notifyChange(value, nodes, e);
  364. }
  365. }
  366. }
  367. // Scenes that may trigger focus:
  368. // 1、click selection
  369. _notifyFocus(e: any) {
  370. this._adapter.notifyFocus(e);
  371. }
  372. // Scenes that may trigger blur
  373. // 1、clickOutSide
  374. // 2、click option / press enter, and then select complete(when multiple is false
  375. // 3、press esc when dropdown list open
  376. _notifyBlur(e: any) {
  377. this._adapter.notifyBlur(e);
  378. }
  379. toggleHoverState(bool: boolean) {
  380. this._adapter.toggleHovering(bool);
  381. }
  382. open() {
  383. this._adapter.openMenu();
  384. this._setDropdownWidth();
  385. this._adapter.registerClickOutsideHandler(e => {
  386. this.close(e);
  387. });
  388. }
  389. close(e: any) {
  390. this._adapter.closeMenu();
  391. this._adapter.unregisterClickOutsideHandler();
  392. this._notifyBlur(e);
  393. if (this.getProp('motionExpand')) {
  394. this._adapter.updateState({ motionKeys: new Set([]) });
  395. }
  396. }
  397. handleClick(e: any) {
  398. const isDisabled = this._isDisabled();
  399. const { isOpen, inputValue } = this.getStates();
  400. const { searchPosition } = this.getProps();
  401. if (isDisabled) {
  402. return;
  403. } else if (!isOpen) {
  404. this.open();
  405. this._notifyFocus(e);
  406. } else if (isOpen) {
  407. if (searchPosition === 'trigger' && inputValue) {
  408. return;
  409. }
  410. this.close(e);
  411. }
  412. }
  413. /**
  414. * A11y: simulate selection click
  415. */
  416. /* istanbul ignore next */
  417. handleSelectionEnterPress(e: any) {
  418. if (isEnterPress(e)) {
  419. this.handleClick(e);
  420. }
  421. }
  422. handleClear(e: any) {
  423. const { searchPosition, filterTreeNode } = this.getProps();
  424. const { inputValue, selectedKeys } = this.getStates();
  425. const isMultiple = this._isMultiple();
  426. const isControlled = this._isControlledComponent();
  427. const value: string | string[] | undefined = isMultiple ? [] : undefined;
  428. this._notifyChange(value, e);
  429. if (!isControlled) {
  430. // reposition dropdown when selected values change
  431. this._adapter.rePositionDropdown();
  432. this._adapter.updateState({
  433. selectedKeys: [],
  434. checkedKeys: new Set(),
  435. halfCheckedKeys: new Set(),
  436. realCheckedKeys: new Set([]),
  437. });
  438. }
  439. // When triggerSearch, clicking the clear button will trigger to clear Input
  440. if (filterTreeNode && searchPosition === strings.SEARCH_POSITION_TRIGGER) {
  441. if (inputValue !== '') {
  442. if (isEmpty(selectedKeys)) {
  443. this.handleInputChange('');
  444. } else {
  445. this.clearInput();
  446. }
  447. }
  448. }
  449. }
  450. /**
  451. * A11y: simulate clear button click
  452. */
  453. /* istanbul ignore next */
  454. handleClearEnterPress(e: any) {
  455. if (isEnterPress(e)) {
  456. this.handleClear(e);
  457. }
  458. }
  459. removeTag(eventKey: BasicTreeNodeData['key']) {
  460. const { disableStrictly, checkRelation } = this.getProps();
  461. const { keyEntities, disabledKeys, realCheckedKeys } = this.getStates();
  462. const item = keyEntities[eventKey].data;
  463. if (item.disabled || (disableStrictly && disabledKeys.has(eventKey))) {
  464. return;
  465. }
  466. if (checkRelation === 'unRelated') {
  467. const newRealCheckedKeys = new Set(realCheckedKeys);
  468. newRealCheckedKeys.delete(eventKey);
  469. this._notifyChange([...newRealCheckedKeys], null);
  470. if (!this._isControlledComponent()) {
  471. this._adapter.updateState({ realCheckedKeys: newRealCheckedKeys } as any);
  472. this._adapter.rePositionDropdown();
  473. }
  474. } else if (checkRelation === 'related') {
  475. const { checkedKeys, halfCheckedKeys } = this.calcCheckedKeys(eventKey, false);
  476. this._notifyChange([...checkedKeys], null);
  477. if (!this._isControlledComponent()) {
  478. this._adapter.updateState({ checkedKeys, halfCheckedKeys });
  479. this._adapter.rePositionDropdown();
  480. }
  481. }
  482. this._adapter.notifySelect(eventKey, false, item);
  483. // reposition dropdown when selected values change
  484. this._adapter.rePositionDropdown();
  485. }
  486. clearInput() {
  487. const { flattenNodes, expandedKeys, selectedKeys, keyEntities, treeData } = this.getStates();
  488. const newExpandedKeys: Set<string> = new Set(expandedKeys);
  489. const isExpandControlled = this._isExpandControlled();
  490. const expandedOptsKeys = findAncestorKeys(selectedKeys, keyEntities);
  491. expandedOptsKeys.forEach(item => newExpandedKeys.add(item));
  492. const newFlattenNodes = flattenTreeData(treeData, newExpandedKeys);
  493. this._adapter.updateState({
  494. expandedKeys: isExpandControlled ? expandedKeys : newExpandedKeys,
  495. flattenNodes: isExpandControlled ? flattenNodes : newFlattenNodes,
  496. inputValue: '',
  497. motionKeys: new Set([]),
  498. filteredKeys: new Set([]),
  499. filteredExpandedKeys: new Set(expandedOptsKeys),
  500. filteredShownKeys: new Set([])
  501. });
  502. }
  503. handleInputChange(sugInput: string) {
  504. // Input is used as controlled component
  505. this._adapter.updateInputValue(sugInput);
  506. const { flattenNodes, expandedKeys, selectedKeys, keyEntities, treeData } = this.getStates();
  507. const { showFilteredOnly, filterTreeNode, treeNodeFilterProp } = this.getProps();
  508. const newExpandedKeys: Set<string> = new Set(expandedKeys);
  509. let filteredOptsKeys: string[] = [];
  510. let expandedOptsKeys = [];
  511. let newFlattenNodes = [];
  512. let filteredShownKeys = new Set([]);
  513. if (!sugInput) {
  514. expandedOptsKeys = findAncestorKeys(selectedKeys, keyEntities);
  515. expandedOptsKeys.forEach(item => newExpandedKeys.add(item));
  516. newFlattenNodes = flattenTreeData(treeData, newExpandedKeys);
  517. } else {
  518. filteredOptsKeys = Object.values(keyEntities)
  519. .filter((item: BasicKeyEntity) => {
  520. const { data } = item;
  521. return filter(sugInput, data, filterTreeNode, treeNodeFilterProp);
  522. })
  523. .map((item: BasicKeyEntity) => item.key);
  524. expandedOptsKeys = findAncestorKeys(filteredOptsKeys, keyEntities, false);
  525. const shownChildKeys = findDescendantKeys(filteredOptsKeys, keyEntities, true);
  526. filteredShownKeys = new Set([...shownChildKeys, ...expandedOptsKeys]);
  527. newFlattenNodes = flattenTreeData(treeData, new Set(expandedOptsKeys), showFilteredOnly && filteredShownKeys);
  528. }
  529. const newFilteredExpandedKeys = new Set(expandedOptsKeys);
  530. this._adapter.notifySearch(sugInput, Array.from(newFilteredExpandedKeys));
  531. this._adapter.updateState({
  532. expandedKeys: this._isExpandControlled() ? expandedKeys : newExpandedKeys,
  533. flattenNodes: this._isExpandControlled() ? flattenNodes : newFlattenNodes,
  534. motionKeys: new Set([]),
  535. filteredKeys: new Set(filteredOptsKeys),
  536. filteredExpandedKeys: newFilteredExpandedKeys,
  537. filteredShownKeys,
  538. });
  539. }
  540. handleNodeSelect(e: any, treeNode: BasicTreeNodeProps) {
  541. const isDisabled = this._isDisabled(treeNode);
  542. if (isDisabled) {
  543. return;
  544. }
  545. if (!this._isMultiple()) {
  546. this.handleSingleSelect(e, treeNode);
  547. } else {
  548. this.handleMultipleSelect(e, treeNode);
  549. }
  550. }
  551. handleSingleSelect(e: any, treeNode: BasicTreeNodeProps) {
  552. let { selectedKeys } = this.getCopyFromState('selectedKeys');
  553. const { clickToHide } = this.getProps();
  554. const { selected, eventKey, data } = treeNode;
  555. this._adapter.notifySelect(eventKey, true, data);
  556. if (!selectedKeys.includes(eventKey) && !selected) {
  557. selectedKeys = [eventKey];
  558. this._notifyChange(eventKey, e);
  559. if (!this._isControlledComponent()) {
  560. this._adapter.updateState({ selectedKeys });
  561. }
  562. }
  563. if (clickToHide && (this._isSelectToClose() || !data.children)) {
  564. this.close(e);
  565. }
  566. }
  567. calcCheckedKeys(eventKey: BasicTreeNodeProps['eventKey'], targetStatus: boolean) {
  568. const { keyEntities } = this.getStates();
  569. const {
  570. checkedKeys,
  571. halfCheckedKeys
  572. } = this.getCopyFromState(['checkedKeys', 'halfCheckedKeys']);
  573. if (targetStatus) {
  574. return calcCheckedKeysForChecked(eventKey, keyEntities, checkedKeys, halfCheckedKeys);
  575. } else {
  576. return calcCheckedKeysForUnchecked(eventKey, keyEntities, checkedKeys, halfCheckedKeys);
  577. }
  578. }
  579. handleMultipleSelect(e: any, treeNode: BasicTreeNodeProps) {
  580. const { searchPosition, disableStrictly, checkRelation } = this.getProps();
  581. const { inputValue, realCheckedKeys } = this.getStates();
  582. const { checked, eventKey, data } = treeNode;
  583. if (checkRelation === 'related') {
  584. const targetStatus = disableStrictly ?
  585. this.calcCheckedStatus(!checked, eventKey) :
  586. !checked;
  587. const { checkedKeys, halfCheckedKeys } = disableStrictly ?
  588. this.calcNonDisabledCheckedKeys(eventKey, targetStatus) :
  589. this.calcCheckedKeys(eventKey, targetStatus);
  590. this._adapter.notifySelect(eventKey, targetStatus, data);
  591. this._notifyChange([...checkedKeys], e);
  592. if (!this._isControlledComponent()) {
  593. this._adapter.updateState({ checkedKeys, halfCheckedKeys });
  594. this._adapter.rePositionDropdown();
  595. }
  596. } else if (checkRelation === 'unRelated') {
  597. const newRealCheckedKeys: Set<string> = new Set(realCheckedKeys);
  598. let targetStatus: boolean;
  599. if (realCheckedKeys.has(eventKey)) {
  600. newRealCheckedKeys.delete(eventKey);
  601. targetStatus = false;
  602. } else {
  603. newRealCheckedKeys.add(eventKey);
  604. targetStatus = true;
  605. }
  606. this._adapter.notifySelect(eventKey, targetStatus, data);
  607. this._notifyChange([...newRealCheckedKeys], e);
  608. if (!this._isControlledComponent()) {
  609. this._adapter.updateState({ realCheckedKeys: newRealCheckedKeys });
  610. this._adapter.rePositionDropdown();
  611. }
  612. }
  613. if (searchPosition === strings.SEARCH_POSITION_TRIGGER && inputValue !== '') {
  614. this._adapter.updateState({ inputValue: '' });
  615. }
  616. }
  617. calcNonDisabledCheckedKeys(eventKey: string, targetStatus: boolean) {
  618. const { keyEntities, disabledKeys } = this.getStates();
  619. const { checkedKeys } = this.getCopyFromState(['checkedKeys']);
  620. const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
  621. const hasDisabled = descendantKeys.some(key => disabledKeys.has(key));
  622. if (!hasDisabled) {
  623. return this.calcCheckedKeys(eventKey, targetStatus);
  624. }
  625. const nonDisabled = descendantKeys.filter(key => !disabledKeys.has(key));
  626. const newCheckedKeys = targetStatus ?
  627. [...nonDisabled, ...checkedKeys] :
  628. difference(normalizeKeyList([...checkedKeys], keyEntities, true), nonDisabled);
  629. return calcCheckedKeys(newCheckedKeys, keyEntities);
  630. }
  631. calcCheckedStatus(targetStatus: boolean, eventKey: string) {
  632. if (!targetStatus) {
  633. return targetStatus;
  634. }
  635. const { checkedKeys, keyEntities, disabledKeys } = this.getStates();
  636. const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
  637. const hasDisabled = descendantKeys.some(key => disabledKeys.has(key));
  638. if (!hasDisabled) {
  639. return targetStatus;
  640. }
  641. const nonDisabledKeys = descendantKeys.filter(key => !disabledKeys.has(key));
  642. const allChecked = nonDisabledKeys.every(key => checkedKeys.has(key));
  643. return !allChecked;
  644. }
  645. handleNodeExpandInSearch(e: any, treeNode: BasicTreeNodeProps) {
  646. const { treeData, filteredShownKeys, keyEntities } = this.getStates();
  647. const showFilteredOnly = this._showFilteredOnly();
  648. // clone otherwise will be modified unexpectedly
  649. const { filteredExpandedKeys } = this.getCopyFromState('filteredExpandedKeys');
  650. let motionType = 'show';
  651. const { eventKey, expanded, data } = treeNode;
  652. // debugger;
  653. if (!expanded) {
  654. filteredExpandedKeys.add(eventKey);
  655. } else if (filteredExpandedKeys.has(eventKey)) {
  656. filteredExpandedKeys.delete(eventKey);
  657. motionType = 'hide';
  658. }
  659. // cache prev flattenNodes, otherwise the calculation will remove hidden items
  660. this._adapter.cacheFlattenNodes(motionType === 'hide' && this._isAnimated());
  661. if (!this._isExpandControlled()) {
  662. // debugger;
  663. const flattenNodes = flattenTreeData(treeData, filteredExpandedKeys, showFilteredOnly && filteredShownKeys);
  664. const motionKeys = this._isAnimated() ? getMotionKeys(eventKey, filteredExpandedKeys, keyEntities) : [];
  665. const newState = {
  666. filteredExpandedKeys,
  667. flattenNodes,
  668. motionKeys: new Set(motionKeys),
  669. motionType,
  670. };
  671. this._adapter.updateState(newState);
  672. }
  673. this._adapter.notifyExpand(filteredExpandedKeys, {
  674. expanded: !expanded,
  675. node: data,
  676. });
  677. }
  678. handleNodeExpand(e: any, treeNode: BasicTreeNodeProps) {
  679. // debugger;
  680. const { loadData } = this.getProps();
  681. const { inputValue, keyEntities } = this.getStates();
  682. const isSearching = Boolean(inputValue);
  683. if (!loadData && (!treeNode.children || !treeNode.children.length)) {
  684. return;
  685. }
  686. const isExpandControlled = this._isExpandControlled();
  687. if (isSearching && !isExpandControlled) {
  688. this.handleNodeExpandInSearch(e, treeNode);
  689. return;
  690. }
  691. const { treeData } = this.getStates();
  692. // clone otherwise will be modified unexpectedly
  693. const { expandedKeys } = this.getCopyFromState('expandedKeys');
  694. let motionType = 'show';
  695. const { eventKey, expanded, data } = treeNode;
  696. if (!expanded) {
  697. expandedKeys.add(eventKey);
  698. } else if (expandedKeys.has(eventKey)) {
  699. expandedKeys.delete(eventKey);
  700. motionType = 'hide';
  701. }
  702. this._adapter.cacheFlattenNodes(motionType === 'hide' && this._isAnimated());
  703. if (!isExpandControlled) {
  704. // debugger;
  705. const flattenNodes = flattenTreeData(treeData, expandedKeys);
  706. const motionKeys = this._isAnimated() ? getMotionKeys(eventKey, expandedKeys, keyEntities) : [];
  707. const newState = {
  708. expandedKeys,
  709. flattenNodes,
  710. motionKeys: new Set(motionKeys),
  711. motionType,
  712. };
  713. this._adapter.updateState(newState);
  714. }
  715. this._adapter.notifyExpand(expandedKeys, {
  716. expanded: !expanded,
  717. node: data,
  718. });
  719. }
  720. /**
  721. * The selected items that need to be displayed in the search box when obtaining a single selection
  722. */
  723. getRenderTextInSingle() {
  724. const { renderSelectedItem: propRenderSelectedItem, treeNodeLabelProp } = this.getProps();
  725. const { selectedKeys, keyEntities } = this.getStates();
  726. const renderSelectedItem = isFunction(propRenderSelectedItem) ?
  727. propRenderSelectedItem :
  728. (item: BasicTreeNodeData) => get(item, treeNodeLabelProp, null);
  729. const item = selectedKeys.length && keyEntities[selectedKeys[0]] ?
  730. keyEntities[selectedKeys[0]].data :
  731. undefined;
  732. const renderText = item && treeNodeLabelProp in item ? renderSelectedItem(item) : null;
  733. return renderText;
  734. }
  735. /**
  736. * When the search box is on the trigger, the blur event handling method
  737. */
  738. handleInputTriggerBlur() {
  739. this._adapter.updateState({
  740. inputTriggerFocus: false
  741. });
  742. }
  743. /**
  744. * When the search box is on the trigger, the focus event processing method
  745. */
  746. handleInputTriggerFocus() {
  747. this.clearInput();
  748. this._adapter.updateState({
  749. inputTriggerFocus: true
  750. });
  751. }
  752. setLoadKeys(data: BasicTreeNodeData, resolve: (value?: any) => void) {
  753. this._adapter.updateLoadKeys(data, resolve);
  754. }
  755. }