UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

1,085 lines 37 kB
import _isObject from "lodash/isObject"; import _noop from "lodash/noop"; import _isNumber from "lodash/isNumber"; import _isFunction from "lodash/isFunction"; import _isEmpty from "lodash/isEmpty"; import _isString from "lodash/isString"; import _isEqual from "lodash/isEqual"; import _isSet from "lodash/isSet"; import React, { Fragment } from 'react'; import cls from 'classnames'; import PropTypes from 'prop-types'; import CascaderFoundation from '@douyinfe/semi-foundation/lib/es/cascader/foundation'; import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/cascader/constants'; import { numbers as popoverNumbers } from '@douyinfe/semi-foundation/lib/es/popover/constants'; import '@douyinfe/semi-foundation/lib/es/cascader/cascader.css'; import { IconClear, IconChevronDown } from '@douyinfe/semi-icons'; import { convertDataToEntities, calcMergeType, getKeyByValuePath } from '@douyinfe/semi-foundation/lib/es/cascader/util'; import { calcCheckedKeys, normalizeKeyList, calcDisabledKeys } from '@douyinfe/semi-foundation/lib/es/tree/treeUtil'; import ConfigContext from '../configProvider/context'; import BaseComponent from '../_base/baseComponent'; import Input from '../input/index'; import Popover from '../popover/index'; import Item from './item'; import Trigger from '../trigger'; import Tag from '../tag'; import TagInput from '../tagInput'; import { getDefaultPropsFromGlobalConfig, isSemiIcon } from '../_utils'; const prefixcls = cssClasses.PREFIX; const resetkey = 0; class Cascader extends BaseComponent { constructor(props) { super(props); // ref method this.search = value => { this.handleInputChange(value); }; this.handleInputChange = value => { this.foundation.handleInputChange(value); }; this.handleTagRemoveInTrigger = pos => { this.foundation.handleTagRemoveInTrigger(pos); }; this.handleTagClose = (tagChildren, e, tagKey) => { // When value has not changed, prevent clicking tag closeBtn to close tag e.preventDefault(); this.foundation.handleTagRemoveByKey(tagKey); }; this.renderTagItem = (nodeKey, idx) => { const { keyEntities, disabledKeys } = this.state; const { size, disabled, displayProp, displayRender, disableStrictly } = this.props; if (keyEntities[nodeKey]) { const isDisabled = disabled || keyEntities[nodeKey].data.disabled || disableStrictly && disabledKeys.has(nodeKey); const tagCls = cls(`${prefixcls}-selection-tag`, { [`${prefixcls}-selection-tag-disabled`]: isDisabled }); // custom render tags if (_isFunction(displayRender)) { return displayRender(keyEntities[nodeKey], idx); // default render tags } else { return /*#__PURE__*/React.createElement(Tag, { size: size === 'default' ? 'large' : size, key: `tag-${nodeKey}-${idx}`, color: "white", tagKey: nodeKey, className: tagCls, closable: true, onClose: this.handleTagClose }, keyEntities[nodeKey].data[displayProp]); } } return null; }; this.onRemoveInTagInput = v => { this.foundation.handleTagRemoveByKey(v); }; this.handleItemClick = (e, item) => { this.foundation.handleItemClick(e, item); }; this.handleItemHover = (e, item) => { this.foundation.handleItemHover(e, item); }; this.onItemCheckboxClick = item => { this.foundation.onItemCheckboxClick(item); }; this.handleListScroll = (e, ind) => { this.foundation.handleListScroll(e, ind); }; this.renderContent = () => { const { inputValue, isSearching, activeKeys, selectedKeys, checkedKeys, halfCheckedKeys, loadedKeys, loadingKeys } = this.state; const { filterTreeNode, dropdownClassName, dropdownStyle, loadData, emptyContent, separator, topSlot, bottomSlot, showNext, multiple, filterRender, virtualizeInSearch, expandIcon } = this.props; const searchable = Boolean(filterTreeNode) && isSearching; const popoverCls = cls(dropdownClassName, `${prefixcls}-popover`); const renderData = this.foundation.getRenderData(); const isEmpty = !renderData || !renderData.length; const realDropDownStyle = isEmpty ? Object.assign(Object.assign({}, dropdownStyle), { minWidth: this.state.emptyContentMinWidth }) : dropdownStyle; const content = /*#__PURE__*/React.createElement("div", { className: popoverCls, role: "listbox", style: realDropDownStyle, onKeyDown: this.foundation.handleKeyDown, ref: this.optionContainerEl }, topSlot, /*#__PURE__*/React.createElement(Item, { activeKeys: activeKeys, selectedKeys: selectedKeys, separator: separator, loadedKeys: loadedKeys, loadingKeys: loadingKeys, onItemClick: this.handleItemClick, onItemHover: this.handleItemHover, showNext: showNext, onItemCheckboxClick: this.onItemCheckboxClick, onListScroll: this.handleListScroll, searchable: searchable, keyword: inputValue, emptyContent: emptyContent, loadData: loadData, data: renderData, multiple: multiple, checkedKeys: checkedKeys, halfCheckedKeys: halfCheckedKeys, filterRender: filterRender, virtualize: virtualizeInSearch, expandIcon: expandIcon }), bottomSlot); return content; }; this.renderPlusN = hiddenTag => { const { disabled, showRestTagsPopover, restTagsPopoverProps } = this.props; const plusNCls = cls(`${prefixcls}-selection-n`, { [`${prefixcls}-selection-n-disabled`]: disabled }); const renderPlusNChildren = /*#__PURE__*/React.createElement("span", { className: plusNCls }, "+", hiddenTag.length); return showRestTagsPopover ? (/*#__PURE__*/React.createElement(Popover, Object.assign({ content: hiddenTag, showArrow: true, trigger: "hover", position: "top", autoAdjustOverflow: true }, restTagsPopoverProps), renderPlusNChildren)) : renderPlusNChildren; }; this.renderMultipleTags = () => { const { autoMergeValue, maxTagCount, checkRelation } = this.props; const { checkedKeys, resolvedCheckedKeys } = this.state; const realKeys = this.mergeType === strings.NONE_MERGE_TYPE || checkRelation === strings.UN_RELATED ? checkedKeys : resolvedCheckedKeys; const displayTag = []; const hiddenTag = []; [...realKeys].forEach((checkedKey, idx) => { const notExceedMaxTagCount = !_isNumber(maxTagCount) || maxTagCount >= idx + 1; const item = this.renderTagItem(checkedKey, idx); if (notExceedMaxTagCount) { displayTag.push(item); } else { hiddenTag.push(item); } }); return /*#__PURE__*/React.createElement(React.Fragment, null, displayTag, !_isEmpty(hiddenTag) && this.renderPlusN(hiddenTag)); }; this.renderDisplayText = () => { const { displayProp, separator, displayRender } = this.props; const { selectedKeys } = this.state; let displayText = ''; if (selectedKeys.size) { const displayPath = this.foundation.getItemPropPath([...selectedKeys][0], displayProp); if (displayRender && typeof displayRender === 'function') { displayText = displayRender(displayPath); } else { displayText = displayPath.map((path, index) => (/*#__PURE__*/React.createElement(Fragment, { key: `${path}-${index}` }, index < displayPath.length - 1 ? (/*#__PURE__*/React.createElement(React.Fragment, null, path, separator)) : path))); } } return displayText; }; this.renderSelectContent = () => { const { placeholder, filterTreeNode, multiple, searchPosition } = this.props; const { checkedKeys } = this.state; const searchable = Boolean(filterTreeNode) && searchPosition === strings.SEARCH_POSITION_TRIGGER; if (!searchable) { if (multiple) { if (checkedKeys.size === 0) { return /*#__PURE__*/React.createElement("span", { className: `${prefixcls}-selection-placeholder` }, placeholder); } return this.renderMultipleTags(); } else { const displayText = this.renderDisplayText(); const spanCls = cls({ [`${prefixcls}-selection-placeholder`]: !displayText }); return /*#__PURE__*/React.createElement("span", { className: spanCls }, displayText ? displayText : placeholder); } } const input = multiple ? this.renderTagInput() : this.renderInput(); return input; }; this.renderSuffix = () => { const { suffix } = this.props; const suffixWrapperCls = cls({ [`${prefixcls}-suffix`]: true, [`${prefixcls}-suffix-text`]: suffix && _isString(suffix), [`${prefixcls}-suffix-icon`]: isSemiIcon(suffix) }); return /*#__PURE__*/React.createElement("div", { className: suffixWrapperCls, "x-semi-prop": "suffix" }, suffix); }; this.renderPrefix = () => { const { prefix, insetLabel, insetLabelId } = this.props; const labelNode = prefix || insetLabel; const prefixWrapperCls = cls({ [`${prefixcls}-prefix`]: true, // to be doublechecked [`${prefixcls}-inset-label`]: insetLabel, [`${prefixcls}-prefix-text`]: labelNode && _isString(labelNode), [`${prefixcls}-prefix-icon`]: isSemiIcon(labelNode) }); return /*#__PURE__*/React.createElement("div", { className: prefixWrapperCls, id: insetLabelId, "x-semi-prop": "prefix,insetLabel" }, labelNode); }; this.renderCustomTrigger = () => { var _a; const { disabled, triggerRender, multiple } = this.props; const { selectedKeys, inputValue, inputPlaceHolder, resolvedCheckedKeys, checkedKeys, keyEntities } = this.state; let realValue; if (multiple) { if (this.mergeType === strings.NONE_MERGE_TYPE) { realValue = new Set(); checkedKeys.forEach(key => { var _a; realValue.add((_a = keyEntities[key]) === null || _a === void 0 ? void 0 : _a.pos); }); } else { realValue = new Set(); resolvedCheckedKeys.forEach(key => { var _a; realValue.add((_a = keyEntities[key]) === null || _a === void 0 ? void 0 : _a.pos); }); } } else { realValue = (_a = keyEntities[[...selectedKeys][0]]) === null || _a === void 0 ? void 0 : _a.pos; } return /*#__PURE__*/React.createElement(Trigger, { value: realValue, inputValue: inputValue, onChange: this.handleInputChange, onClear: this.handleClear, placeholder: inputPlaceHolder, disabled: disabled, triggerRender: triggerRender, componentName: 'Cascader', componentProps: Object.assign({}, this.props), onSearch: this.handleInputChange, onRemove: this.handleTagRemoveInTrigger }); }; this.handleMouseOver = () => { this.foundation.toggleHoverState(true); }; this.handleMouseLeave = () => { this.foundation.toggleHoverState(false); }; this.handleClear = e => { e && e.stopPropagation(); this.foundation.handleClear(); }; /** * A11y: simulate clear button click */ /* istanbul ignore next */ this.handleClearEnterPress = e => { e && e.stopPropagation(); this.foundation.handleClearEnterPress(e); }; this.showClearBtn = () => { const { showClear, disabled, multiple } = this.props; const { selectedKeys, isOpen, isHovering, checkedKeys, inputValue } = this.state; const hasValue = selectedKeys.size; const multipleWithHaveValue = multiple && checkedKeys.size; return showClear && (inputValue || hasValue || multipleWithHaveValue) && !disabled && (isOpen || isHovering); }; this.renderClearBtn = () => { const clearCls = cls(`${prefixcls}-clearbtn`); const { clearIcon } = this.props; const allowClear = this.showClearBtn(); if (allowClear) { return /*#__PURE__*/React.createElement("div", { className: clearCls, onClick: this.handleClear, onKeyPress: this.handleClearEnterPress, role: "button", tabIndex: 0 }, clearIcon ? clearIcon : /*#__PURE__*/React.createElement(IconClear, null)); } return null; }; this.renderArrow = () => { const { arrowIcon } = this.props; const showClearBtn = this.showClearBtn(); if (showClearBtn) { return null; } return arrowIcon ? (/*#__PURE__*/React.createElement("div", { className: cls(`${prefixcls}-arrow`), "x-semi-prop": "arrowIcon" }, arrowIcon)) : null; }; this.renderSelection = () => { const { disabled, multiple, filterTreeNode, style, size, className, validateStatus, prefix, suffix, insetLabel, triggerRender, showClear, id, borderless } = this.props; const { isOpen, isFocus, isInput, checkedKeys } = this.state; const filterable = Boolean(filterTreeNode); const useCustomTrigger = typeof triggerRender === 'function'; const classNames = useCustomTrigger ? cls(className) : cls(prefixcls, className, { [`${prefixcls}-borderless`]: borderless, [`${prefixcls}-focus`]: isFocus || isOpen && !isInput, [`${prefixcls}-disabled`]: disabled, [`${prefixcls}-single`]: true, [`${prefixcls}-filterable`]: filterable, [`${prefixcls}-error`]: validateStatus === 'error', [`${prefixcls}-warning`]: validateStatus === 'warning', [`${prefixcls}-small`]: size === 'small', [`${prefixcls}-large`]: size === 'large', [`${prefixcls}-with-prefix`]: prefix || insetLabel, [`${prefixcls}-with-suffix`]: suffix }); const mouseEvent = showClear ? { onMouseEnter: () => this.handleMouseOver(), onMouseLeave: () => this.handleMouseLeave() } : {}; const sectionCls = cls(`${prefixcls}-selection`, { [`${prefixcls}-selection-multiple`]: multiple && !_isEmpty(checkedKeys) }); const inner = useCustomTrigger ? this.renderCustomTrigger() : [/*#__PURE__*/React.createElement(Fragment, { key: 'prefix' }, prefix || insetLabel ? this.renderPrefix() : null), /*#__PURE__*/React.createElement(Fragment, { key: 'selection' }, /*#__PURE__*/React.createElement("div", { className: sectionCls }, this.renderSelectContent())), /*#__PURE__*/React.createElement(Fragment, { key: 'suffix' }, suffix ? this.renderSuffix() : null), /*#__PURE__*/React.createElement(Fragment, { key: 'clearbtn' }, this.renderClearBtn()), /*#__PURE__*/React.createElement(Fragment, { key: 'arrow' }, this.renderArrow())]; /** * Reasons for disabling the a11y eslint rule: * The following attributes(aria-controls,aria-expanded) will be automatically added by Tooltip, no need to declare here */ return /*#__PURE__*/React.createElement("div", Object.assign({ className: classNames, style: style, ref: this.triggerRef, onClick: e => this.foundation.handleClick(e), onKeyPress: e => this.foundation.handleSelectionEnterPress(e), "aria-invalid": this.props['aria-invalid'], "aria-errormessage": this.props['aria-errormessage'], "aria-label": this.props['aria-label'], "aria-labelledby": this.props['aria-labelledby'], "aria-describedby": this.props['aria-describedby'], "aria-required": this.props['aria-required'], id: id, onKeyDown: this.foundation.handleKeyDown }, mouseEvent, { // eslint-disable-next-line jsx-a11y/role-has-required-aria-props role: "combobox", tabIndex: 0 }, this.getDataAttr(this.props)), inner); }; this.state = { emptyContentMinWidth: null, disabledKeys: new Set(), isOpen: props.defaultOpen, /* By changing rePosKey, the dropdown position can be refreshed */ rePosKey: resetkey, /* A data structure for storing cascader data items */ keyEntities: {}, /* Selected and show tick icon */ selectedKeys: new Set([]), /* The key of the activated node */ activeKeys: new Set([]), /* The key of the filtered node */ filteredKeys: new Set([]), /* Value of input box */ inputValue: '', /* Is searching */ isSearching: false, /* The placeholder of input box */ inputPlaceHolder: props.searchPlaceholder || props.placeholder, /* Cache props */ prevProps: {}, /* Is hovering */ isHovering: false, /* Key of checked node, when multiple */ checkedKeys: new Set([]), /* Key of half checked node, when multiple */ halfCheckedKeys: new Set([]), /* Auto merged checkedKeys or leaf checkedKeys, when multiple */ resolvedCheckedKeys: new Set([]), /* Keys of loaded item */ loadedKeys: new Set(), /* Keys of loading item */ loadingKeys: new Set(), /* Mark whether this rendering has triggered asynchronous loading of data */ loading: false, showInput: false }; this.options = {}; this.isEmpty = false; this.mergeType = calcMergeType(props.autoMergeValue, props.leafOnly); this.inputRef = /*#__PURE__*/React.createRef(); this.triggerRef = /*#__PURE__*/React.createRef(); this.optionsRef = /*#__PURE__*/React.createRef(); this.optionContainerEl = /*#__PURE__*/React.createRef(); this.clickOutsideHandler = null; this.foundation = new CascaderFoundation(this.adapter); this.loadingKeysRef = /*#__PURE__*/React.createRef(); this.loadedKeysRef = /*#__PURE__*/React.createRef(); } get adapter() { var _this = this; const filterAdapter = { updateInputValue: value => { this.setState({ inputValue: value }); }, updateInputPlaceHolder: value => { this.setState({ inputPlaceHolder: value }); }, focusInput: () => { const { preventScroll } = this.props; if (this.inputRef && this.inputRef.current) { // TODO: check the reason this.inputRef.current.focus({ preventScroll }); } }, blurInput: () => { if (this.inputRef && this.inputRef.current) { this.inputRef.current.blur(); } } }; const cascaderAdapter = { registerClickOutsideHandler: cb => { const clickOutsideHandler = e => { const triggerDom = this.triggerRef && this.triggerRef.current; const optionsDom = this.optionContainerEl && this.optionContainerEl.current; const target = e.target; const path = e.composedPath && e.composedPath() || [target]; if (optionsDom && (!optionsDom.contains(target) || !optionsDom.contains(target.parentNode)) && triggerDom && !triggerDom.contains(target) && !(path.includes(triggerDom) || path.includes(optionsDom))) { cb(e); } }; this.clickOutsideHandler = clickOutsideHandler; document.addEventListener('mousedown', clickOutsideHandler, false); }, unregisterClickOutsideHandler: () => { document.removeEventListener('mousedown', this.clickOutsideHandler, false); }, rePositionDropdown: () => { let { rePosKey } = this.state; rePosKey = rePosKey + 1; this.setState({ rePosKey }); } }; return Object.assign(Object.assign(Object.assign(Object.assign({}, super.adapter), filterAdapter), cascaderAdapter), { setEmptyContentMinWidth: minWidth => { this.setState({ emptyContentMinWidth: minWidth }); }, getTriggerWidth: () => { const el = this.triggerRef.current; return el && el.getBoundingClientRect().width; }, updateStates: states => { this.setState(Object.assign({}, states)); }, openMenu: () => { this.setState({ isOpen: true }); }, closeMenu: cb => { this.setState({ isOpen: false }, () => { cb && cb(); }); }, updateSelection: selectedKeys => this.setState({ selectedKeys }), notifyChange: value => { this.props.onChange && this.props.onChange(value); }, notifySelect: selected => { this.props.onSelect && this.props.onSelect(selected); }, notifyOnSearch: input => { this.props.onSearch && this.props.onSearch(input); }, notifyFocus: function () { _this.props.onFocus && _this.props.onFocus(...arguments); }, notifyBlur: function () { _this.props.onBlur && _this.props.onBlur(...arguments); }, notifyDropdownVisibleChange: visible => { this.props.onDropdownVisibleChange(visible); }, toggleHovering: bool => { this.setState({ isHovering: bool }); }, notifyLoadData: (selectedOpt, callback) => { const { loadData } = this.props; if (loadData) { new Promise(resolve => { loadData(selectedOpt).then(() => { /** Why update loading status & call callback function in setTimeout? * loadData func will update treeData, treeData change may trigger * selectedKeys & activeKeys change. For Loading data asynchronously, * activeKeys should not change, Its implementation depends on loading * & loadedKeys. The update time of Loading & loadedKeys(in callback func) * should be later than the update time of treeData(in loaData func) * In React 18, we need to use setTimeout to ensure the above time requirements. * */ setTimeout(() => { callback(); this.setState({ loading: false }); resolve(); }); }); }); } }, notifyOnLoad: (newLoadedKeys, data) => { const { onLoad } = this.props; onLoad && onLoad(newLoadedKeys, data); }, notifyListScroll: (e, _ref) => { let { panelIndex, activeNode } = _ref; this.props.onListScroll(e, { panelIndex, activeNode }); }, notifyOnExceed: data => this.props.onExceed(data), notifyClear: () => this.props.onClear(), toggleInputShow: (showInput, cb) => { this.setState({ showInput }, () => { cb(); }); }, updateFocusState: isFocus => { this.setState({ isFocus }); }, updateLoadingKeyRefValue: keys => { this.loadingKeysRef.current = keys; }, getLoadingKeyRefValue: () => { return this.loadingKeysRef.current; }, updateLoadedKeyRefValue: keys => { this.loadedKeysRef.current = keys; }, getLoadedKeyRefValue: () => { return this.loadedKeysRef.current; } }); } static getDerivedStateFromProps(props, prevState) { const { multiple, value, defaultValue, onChangeWithObject, leafOnly, autoMergeValue, checkRelation, searchPlaceholder, placeholder } = props; const { prevProps } = prevState; let keyEntities = prevState.keyEntities || {}; const newState = {}; const newPlaceholder = searchPlaceholder || placeholder; if (newPlaceholder !== prevState.inputPlaceHolder) { newState.inputPlaceHolder = newPlaceholder; } const needUpdate = name => { const firstInProps = _isEmpty(prevProps) && name in props; const nameHasChange = prevProps && !_isEqual(prevProps[name], props[name]); return firstInProps || nameHasChange; }; const needUpdateData = () => { const firstInProps = !prevProps && 'treeData' in props; const treeDataHasChange = prevProps && prevProps.treeData !== props.treeData; return firstInProps || treeDataHasChange; }; const getRealKeys = (realValue, keyEntities) => { // normalizedValue is used to save the value in two-dimensional array format let normalizedValue = []; if (Array.isArray(realValue)) { normalizedValue = Array.isArray(realValue[0]) ? realValue : [realValue]; } else { if (realValue !== undefined) { normalizedValue = [[realValue]]; } } // formatValuePath is used to save value of valuePath const formatValuePath = []; normalizedValue.forEach(valueItem => { const formatItem = onChangeWithObject && _isObject(valueItem[0]) ? valueItem.map(i => i === null || i === void 0 ? void 0 : i.value) : valueItem; formatItem.length > 0 && formatValuePath.push(formatItem); }); // formatKeys is used to save key of value const formatKeys = formatValuePath.reduce((acc, cur) => { const key = getKeyByValuePath(cur); keyEntities[key] && acc.push(key); return acc; }, []); return formatKeys; }; if (multiple) { const needUpdateTreeData = needUpdate('treeData') || needUpdateData(); const needUpdateValue = needUpdate('value') || _isEmpty(prevProps) && defaultValue; // when value and treedata need updated if (needUpdateTreeData || needUpdateValue) { // update state.keyEntities if (needUpdateTreeData) { newState.treeData = props.treeData; keyEntities = convertDataToEntities(props.treeData); newState.keyEntities = keyEntities; } let realKeys = prevState.checkedKeys; // when data was updated if (needUpdateValue) { const realValue = needUpdate('value') ? value : defaultValue; realKeys = getRealKeys(realValue, keyEntities); } else { // needUpdateValue is false // if treeData is updated & Cascader is controlled, realKeys should be recalculated if (needUpdateTreeData && 'value' in props) { const realValue = value; realKeys = getRealKeys(realValue, keyEntities); } } if (_isSet(realKeys)) { realKeys = [...realKeys]; } if (checkRelation === strings.RELATED) { const calRes = calcCheckedKeys(realKeys, keyEntities); const checkedKeys = new Set(calRes.checkedKeys); const halfCheckedKeys = new Set(calRes.halfCheckedKeys); // disableStrictly if (props.disableStrictly) { newState.disabledKeys = calcDisabledKeys(keyEntities); } const isLeafOnlyMerge = calcMergeType(autoMergeValue, leafOnly) === strings.LEAF_ONLY_MERGE_TYPE; newState.checkedKeys = checkedKeys; newState.halfCheckedKeys = halfCheckedKeys; newState.resolvedCheckedKeys = new Set(normalizeKeyList(checkedKeys, keyEntities, isLeafOnlyMerge)); } else { newState.checkedKeys = new Set(realKeys); } newState.prevProps = props; } } return newState; } componentDidMount() { this.foundation.init(); } componentWillUnmount() { this.foundation.destroy(); } componentDidUpdate(prevProps) { // multiple mode uses getDerivedStateFromProps to sync keyEntities, // so we also need to sync filteredKeys when treeData updates during searching. if (this.props.multiple) { if (!_isEqual(prevProps.treeData, this.props.treeData)) { this.foundation.recalculateFilteredKeys(); } return; } let isOptionsChanged = false; if (!_isEqual(prevProps.treeData, this.props.treeData)) { isOptionsChanged = true; this.foundation.collectOptions(); } if (prevProps.value !== this.props.value && !isOptionsChanged) { this.foundation.handleValueChange(this.props.value); } } renderTagInput() { const { size, disabled, placeholder, maxTagCount, showRestTagsPopover, restTagsPopoverProps, checkRelation } = this.props; const { inputValue, checkedKeys, keyEntities, resolvedCheckedKeys, inputPlaceHolder } = this.state; const tagInputcls = cls(`${prefixcls}-tagInput-wrapper`); const realKeys = this.mergeType === strings.NONE_MERGE_TYPE || checkRelation === strings.UN_RELATED ? checkedKeys : resolvedCheckedKeys; return /*#__PURE__*/React.createElement(TagInput, { className: tagInputcls, ref: this.inputRef, disabled: disabled, size: size, value: [...realKeys], showRestTagsPopover: showRestTagsPopover, restTagsPopoverProps: restTagsPopoverProps, maxTagCount: maxTagCount, renderTagItem: this.renderTagItem, inputValue: inputValue, onInputChange: this.handleInputChange, // TODO Modify logic, not modify type onRemove: this.onRemoveInTagInput, placeholder: inputPlaceHolder, expandRestTagsOnClick: false }); } renderInput() { const { size, disabled } = this.props; const inputcls = cls(`${prefixcls}-input`); const { inputValue, inputPlaceHolder, showInput } = this.state; const inputProps = { disabled, value: inputValue, className: inputcls, onChange: this.handleInputChange }; const wrappercls = cls({ [`${prefixcls}-search-wrapper`]: true, [`${prefixcls}-search-wrapper-${size}`]: size !== 'default' }); const displayText = this.renderDisplayText(); const spanCls = cls({ [`${prefixcls}-selection-placeholder`]: !displayText, [`${prefixcls}-selection-text-hide`]: showInput && inputValue, [`${prefixcls}-selection-text-inactive`]: showInput && !inputValue }); return /*#__PURE__*/React.createElement("div", { className: wrappercls }, /*#__PURE__*/React.createElement("span", { className: spanCls }, displayText ? displayText : inputPlaceHolder), showInput && /*#__PURE__*/React.createElement(Input, Object.assign({ ref: this.inputRef, size: size }, inputProps))); } close() { this.foundation.close(); } open() { this.foundation.open(); } focus() { this.foundation.focus(); } blur() { this.foundation.blur(); } render() { const { zIndex, getPopupContainer, autoAdjustOverflow, stopPropagation, mouseLeaveDelay, mouseEnterDelay, position, motion, dropdownMargin } = this.props; const { isOpen, rePosKey } = this.state; const { direction } = this.context; const content = this.renderContent(); const selection = this.renderSelection(); const pos = position !== null && position !== void 0 ? position : direction === 'rtl' ? 'bottomRight' : 'bottomLeft'; return /*#__PURE__*/React.createElement(Popover, { getPopupContainer: getPopupContainer, zIndex: zIndex, motion: motion, margin: dropdownMargin, ref: this.optionsRef, content: content, visible: isOpen, trigger: "custom", rePosKey: rePosKey, position: pos, autoAdjustOverflow: autoAdjustOverflow, stopPropagation: stopPropagation, mouseLeaveDelay: mouseLeaveDelay, mouseEnterDelay: mouseEnterDelay, afterClose: () => this.foundation.updateSearching(false) }, selection); } } Cascader.__SemiComponentName__ = "Cascader"; Cascader.contextType = ConfigContext; Cascader.propTypes = { 'aria-labelledby': PropTypes.string, 'aria-invalid': PropTypes.bool, 'aria-errormessage': PropTypes.string, 'aria-describedby': PropTypes.string, 'aria-required': PropTypes.bool, 'aria-label': PropTypes.string, arrowIcon: PropTypes.node, borderless: PropTypes.bool, clearIcon: PropTypes.node, changeOnSelect: PropTypes.bool, defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), disabled: PropTypes.bool, dropdownClassName: PropTypes.string, dropdownStyle: PropTypes.object, dropdownMargin: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), emptyContent: PropTypes.node, motion: PropTypes.bool, /* show search input, if passed in a function, used as custom filter */ filterTreeNode: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), filterLeafOnly: PropTypes.bool, placeholder: PropTypes.string, searchPlaceholder: PropTypes.string, size: PropTypes.oneOf(strings.SIZE_SET), style: PropTypes.object, className: PropTypes.string, treeData: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), label: PropTypes.any })), treeNodeFilterProp: PropTypes.string, suffix: PropTypes.node, prefix: PropTypes.node, insetLabel: PropTypes.node, insetLabelId: PropTypes.string, id: PropTypes.string, displayProp: PropTypes.string, displayRender: PropTypes.func, onChange: PropTypes.func, onSearch: PropTypes.func, onSelect: PropTypes.func, onBlur: PropTypes.func, onFocus: PropTypes.func, children: PropTypes.node, getPopupContainer: PropTypes.func, zIndex: PropTypes.number, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]), validateStatus: PropTypes.oneOf(strings.VALIDATE_STATUS), showNext: PropTypes.oneOf([strings.SHOW_NEXT_BY_CLICK, strings.SHOW_NEXT_BY_HOVER]), stopPropagation: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), showClear: PropTypes.bool, defaultOpen: PropTypes.bool, autoAdjustOverflow: PropTypes.bool, onDropdownVisibleChange: PropTypes.func, triggerRender: PropTypes.func, onListScroll: PropTypes.func, onChangeWithObject: PropTypes.bool, bottomSlot: PropTypes.node, topSlot: PropTypes.node, multiple: PropTypes.bool, autoMergeValue: PropTypes.bool, maxTagCount: PropTypes.number, showRestTagsPopover: PropTypes.bool, restTagsPopoverProps: PropTypes.object, max: PropTypes.number, separator: PropTypes.string, onExceed: PropTypes.func, onClear: PropTypes.func, loadData: PropTypes.func, onLoad: PropTypes.func, loadedKeys: PropTypes.array, disableStrictly: PropTypes.bool, leafOnly: PropTypes.bool, enableLeafClick: PropTypes.bool, clickToSelect: PropTypes.bool, preventScroll: PropTypes.bool, position: PropTypes.string, searchPosition: PropTypes.string, remote: PropTypes.bool }; Cascader.defaultProps = getDefaultPropsFromGlobalConfig(Cascader.__SemiComponentName__, { borderless: false, leafOnly: false, arrowIcon: /*#__PURE__*/React.createElement(IconChevronDown, null), stopPropagation: true, motion: true, defaultOpen: false, zIndex: popoverNumbers.DEFAULT_Z_INDEX, showClear: false, autoClearSearchValue: true, changeOnSelect: false, disableStrictly: false, autoMergeValue: true, multiple: false, filterTreeNode: false, filterLeafOnly: true, showRestTagsPopover: false, restTagsPopoverProps: {}, separator: ' / ', size: 'default', treeNodeFilterProp: 'label', displayProp: 'label', treeData: [], showNext: strings.SHOW_NEXT_BY_CLICK, onExceed: _noop, onClear: _noop, onDropdownVisibleChange: _noop, onListScroll: _noop, enableLeafClick: false, clickToSelect: false, 'aria-label': 'Cascader', searchPosition: strings.SEARCH_POSITION_TRIGGER, checkRelation: strings.RELATED, remote: false }); export default Cascader;