foundation.ts 32 KB

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