@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,335 lines (1,334 loc) • 53 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
var _isNumber2 = _interopRequireDefault(require("lodash/isNumber"));
var _get2 = _interopRequireDefault(require("lodash/get"));
var _noop2 = _interopRequireDefault(require("lodash/noop"));
var _isString2 = _interopRequireDefault(require("lodash/isString"));
var _isEqual2 = _interopRequireDefault(require("lodash/isEqual"));
var _react = _interopRequireWildcard(require("react"));
var _reactDom = _interopRequireDefault(require("react-dom"));
var _classnames = _interopRequireDefault(require("classnames"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _context = _interopRequireDefault(require("../configProvider/context"));
var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/select/foundation"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/select/constants");
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _index = _interopRequireDefault(require("../tag/index"));
var _group = _interopRequireDefault(require("../tag/group"));
var _index2 = _interopRequireDefault(require("../overflowList/index"));
var _index3 = _interopRequireDefault(require("../space/index"));
var _text = _interopRequireDefault(require("../typography/text"));
var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer"));
var _index4 = _interopRequireDefault(require("../popover/index"));
var _constants2 = require("@douyinfe/semi-foundation/lib/cjs/popover/constants");
var _Event = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/Event"));
var _reactWindow = require("react-window");
var _utils = require("./utils");
var _virtualRow = _interopRequireDefault(require("./virtualRow"));
var _index5 = _interopRequireDefault(require("../input/index"));
var _option = _interopRequireDefault(require("./option"));
var _optionGroup = _interopRequireDefault(require("./optionGroup"));
var _spin = _interopRequireDefault(require("../spin"));
var _trigger = _interopRequireDefault(require("../trigger"));
var _semiIcons = require("@douyinfe/semi-icons");
var _utils2 = require("../_utils");
var _uuid = require("@douyinfe/semi-foundation/lib/cjs/utils/uuid");
require("@douyinfe/semi-foundation/lib/cjs/select/select.css");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
var __rest = void 0 && (void 0).__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
const prefixcls = _constants.cssClasses.PREFIX;
const key = 0;
// Notes: Use the label of the option as the identifier, that is, the option in Select, the value is allowed to be the same, but the label must be unique
class Select extends _baseComponent.default {
constructor(props) {
super(props);
this.setOptionContainerEl = node => this.optionContainerEl = {
current: node
};
this.handleInputChange = (value, event) => this.foundation.handleInputChange(value, event);
this.getTagItem = (item, i, renderSelectedItem) => {
const {
size,
disabled: selectDisabled
} = this.props;
const label = item[0];
const {
value
} = item[1];
const disabled = item[1].disabled || selectDisabled;
const onClose = (tagContent, e) => {
if (e && typeof e.preventDefault === 'function') {
e.preventDefault(); // make sure that tag will not hidden immediately in controlled mode
}
this.foundation.removeTag({
label,
value
});
};
const {
content,
isRenderInTag
} = renderSelectedItem(item[1], {
index: i,
disabled,
onClose
});
const basic = {
disabled,
closable: !disabled,
onClose
};
if (isRenderInTag) {
return /*#__PURE__*/_react.default.createElement(_index.default, Object.assign({}, basic, {
color: "white",
size: size || 'large',
key: value,
tabIndex: -1
}), content);
} else {
return /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: value
}, content);
}
};
this.state = {
isOpen: false,
isFocus: false,
options: [],
selections: new Map(),
dropdownMinWidth: null,
optionKey: key,
inputValue: '',
showInput: false,
focusIndex: props.defaultActiveFirstOption ? 0 : -1,
keyboardEventSet: {},
optionGroups: [],
isHovering: false,
isFocusInContainer: false,
isFullTags: false,
overflowItemCount: 0
};
/* Generate random string */
this.selectOptionListID = '';
this.selectID = '';
this.virtualizeListRef = /*#__PURE__*/_react.default.createRef();
this.inputRef = /*#__PURE__*/_react.default.createRef();
this.dropdownInputRef = /*#__PURE__*/_react.default.createRef(); // only work when searchPosition = 'dropdown'
this.triggerRef = /*#__PURE__*/_react.default.createRef();
this.optionsRef = /*#__PURE__*/_react.default.createRef();
this.optionContainerEl = /*#__PURE__*/_react.default.createRef();
this.clickOutsideHandler = null;
this.onSelect = this.onSelect.bind(this);
this.onClear = this.onClear.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.renderOption = this.renderOption.bind(this);
this.onKeyPress = this.onKeyPress.bind(this);
this.eventManager = new _Event.default();
this.foundation = new _foundation.default(this.adapter);
}
get adapter() {
var _this = this;
const keyboardAdapter = {
registerKeyDown: cb => {
const keyboardEventSet = {
onKeyDown: cb
};
this.setState({
keyboardEventSet
});
},
unregisterKeyDown: () => {
this.setState({
keyboardEventSet: {}
});
},
updateFocusIndex: focusIndex => {
this.setState({
focusIndex
});
},
scrollToFocusOption: () => {}
};
const filterAdapter = {
updateInputValue: value => {
this.setState({
inputValue: value
});
},
toggleInputShow: (showInput, cb) => {
this.setState({
showInput
}, () => {
cb();
});
},
focusInput: () => {
const {
preventScroll
} = this.props;
if (this.inputRef && this.inputRef.current) {
this.inputRef.current.focus({
preventScroll
});
}
},
focusDropdownInput: () => {
const {
preventScroll
} = this.props;
if (this.dropdownInputRef && this.dropdownInputRef.current) {
this.dropdownInputRef.current.focus({
preventScroll
});
}
}
};
const multipleAdapter = {
notifyMaxLimit: option => this.props.onExceed(option),
getMaxLimit: () => this.props.max,
registerClickOutsideHandler: cb => {
const clickOutsideHandler = e => {
const optionInstance = this.optionsRef && this.optionsRef.current;
const triggerDom = this.triggerRef && this.triggerRef.current;
const optionsDom = _reactDom.default.findDOMNode(optionInstance);
const target = e.target;
const path = e.composedPath && e.composedPath() || [target];
if (!(optionsDom && optionsDom.contains(target)) && !(triggerDom && triggerDom.contains(target)) && !(path.includes(triggerDom) || path.includes(optionsDom))) {
cb(e);
}
};
this.clickOutsideHandler = clickOutsideHandler;
document.addEventListener('mousedown', clickOutsideHandler, false);
},
unregisterClickOutsideHandler: () => {
if (this.clickOutsideHandler) {
document.removeEventListener('mousedown', this.clickOutsideHandler, false);
this.clickOutsideHandler = null;
}
},
rePositionDropdown: () => {
let {
optionKey
} = this.state;
optionKey = optionKey + 1;
this.setState({
optionKey
});
},
notifyDeselect: (value, option) => {
delete option._parentGroup;
this.props.onDeselect(value, option);
}
};
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, super.adapter), keyboardAdapter), filterAdapter), multipleAdapter), {
on: (eventName, eventCallback) => this.eventManager.on(eventName, eventCallback),
off: eventName => this.eventManager.off(eventName),
once: (eventName, eventCallback) => this.eventManager.once(eventName, eventCallback),
emit: eventName => this.eventManager.emit(eventName),
// Collect all subitems, each item is visible by default when collected, and is not selected
getOptionsFromChildren: function () {
let children = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.props.children;
let optionGroups = [];
let options = [];
const {
optionList
} = _this.props;
if (optionList && optionList.length) {
options = optionList.map((itemOpt, index) => Object.assign({
_show: true,
_selected: false,
_scrollIndex: index
}, itemOpt));
optionGroups[0] = {
children: options,
label: ''
};
} else {
const result = (0, _utils.getOptionsFromGroup)(children);
optionGroups = result.optionGroups;
options = result.options;
}
_this.setState({
optionGroups
});
return options;
},
updateOptions: options => {
this.setState({
options
});
},
openMenu: cb => {
this.setState({
isOpen: true
}, () => {
cb === null || cb === void 0 ? void 0 : cb();
});
},
closeMenu: () => {
this.setState({
isOpen: false
});
},
getTriggerWidth: () => {
const el = this.triggerRef.current;
return el && el.getBoundingClientRect().width;
},
setOptionWrapperWidth: width => {
this.setState({
dropdownMinWidth: width
});
},
updateSelection: selections => {
this.setState({
selections
});
},
// clone Map, important!!!, prevent unexpected modify on state
getSelections: () => new Map(this.state.selections),
notifyChange: value => {
this.props.onChange(value);
},
notifySelect: (value, option) => {
delete option._parentGroup;
this.props.onSelect(value, option);
},
notifyDropdownVisibleChange: visible => {
this.props.onDropdownVisibleChange(visible);
},
notifySearch: (input, event) => {
this.props.onSearch(input, event);
},
notifyCreate: input => {
this.props.onCreate(input);
},
notifyMouseEnter: e => {
this.props.onMouseEnter(e);
},
notifyMouseLeave: e => {
this.props.onMouseLeave(e);
},
notifyFocus: event => {
this.props.onFocus(event);
},
notifyBlur: event => {
this.props.onBlur(event);
},
notifyClear: () => {
this.props.onClear();
},
notifyListScroll: e => {
this.props.onListScroll(e);
},
updateHovering: isHovering => {
this.setState({
isHovering
});
},
updateFocusState: isFocus => {
this.setState({
isFocus
});
},
updateOverflowItemCount: overflowItemCount => {
this.setState({
overflowItemCount
});
},
focusTrigger: () => {
try {
const {
preventScroll
} = this.props;
const el = this.triggerRef.current;
el.focus({
preventScroll
});
} catch (error) {}
},
getContainer: () => {
return this.optionContainerEl && this.optionContainerEl.current;
},
getFocusableElements: node => {
return (0, _utils2.getFocusableElements)(node);
},
getActiveElement: () => {
return (0, _utils2.getActiveElement)();
},
setIsFocusInContainer: isFocusInContainer => {
this.setState({
isFocusInContainer
});
},
getIsFocusInContainer: () => {
return this.state.isFocusInContainer;
},
updateScrollTop: index => {
let optionClassName;
if ('renderOptionItem' in this.props) {
optionClassName = `.${prefixcls}-option-custom-selected`;
if (index !== undefined) {
optionClassName = `.${prefixcls}-option-custom:nth-child(${index + 1})`;
}
} else {
optionClassName = `.${prefixcls}-option-selected`;
if (index !== undefined) {
optionClassName = `.${prefixcls}-option:nth-child(${index + 1})`;
}
}
let destNode = document.querySelector(`#${prefixcls}-${this.selectOptionListID} ${optionClassName}`);
if (Array.isArray(destNode)) {
destNode = destNode[0];
}
if (destNode) {
/**
* Scroll the first selected item into view.
* The reason why ScrollIntoView is not used here is that it may cause page to move.
*/
const destParent = destNode.parentNode;
destParent.scrollTop = destNode.offsetTop - destParent.offsetTop - destParent.clientHeight / 2 + destNode.clientHeight / 2;
}
}
});
}
componentDidMount() {
this.foundation.init();
this.selectOptionListID = (0, _uuid.getUuidShort)();
this.selectID = this.props.id || (0, _uuid.getUuidShort)();
}
componentWillUnmount() {
this.foundation.destroy();
}
componentDidUpdate(prevProps, prevState) {
const prevChildrenKeys = _react.default.Children.toArray(prevProps.children).map(child => child.key);
const nowChildrenKeys = _react.default.Children.toArray(this.props.children).map(child => child.key);
let isOptionsChanged = false;
if (!(0, _isEqual2.default)(prevChildrenKeys, nowChildrenKeys) || !(0, _isEqual2.default)(prevProps.optionList, this.props.optionList)) {
isOptionsChanged = true;
this.foundation.handleOptionListChange();
}
// Add isOptionChanged: There may be cases where the value is unchanged, but the optionList is updated. At this time, the label corresponding to the value may change, and the selected item needs to be updated
if (!(0, _isEqual2.default)(this.props.value, prevProps.value) || isOptionsChanged) {
if ('value' in this.props) {
this.foundation.handleValueChange(this.props.value);
} else {
this.foundation.handleOptionListChangeHadDefaultValue();
}
}
}
renderTriggerInput() {
const {
size,
multiple,
disabled,
inputProps,
filter
} = this.props;
const inputPropsCls = (0, _get2.default)(inputProps, 'className');
const inputcls = (0, _classnames.default)(`${prefixcls}-input`, {
[`${prefixcls}-input-single`]: !multiple,
[`${prefixcls}-input-multiple`]: multiple
}, inputPropsCls);
const {
inputValue,
focusIndex
} = this.state;
const selectInputProps = Object.assign({
value: inputValue,
disabled,
className: inputcls,
onChange: this.handleInputChange
}, inputProps);
let style = {};
// Multiple choice mode
if (multiple) {
style = {
width: inputValue ? `${inputValue.length * 16}px` : '2px'
};
selectInputProps.style = style;
}
return /*#__PURE__*/_react.default.createElement(_index5.default, Object.assign({
ref: this.inputRef,
size: size,
"aria-activedescendant": focusIndex !== -1 ? `${this.selectID}-option-${focusIndex}` : '',
onFocus: e => {
// if multiple and filter, when use tab key to let select get focus
// need to manual update state isFocus to let the focus style take effect
if (multiple && Boolean(filter)) {
this.setState({
isFocus: true
});
}
// prevent event bubbling which will fire trigger onFocus event
e.stopPropagation();
// e.nativeEvent.stopImmediatePropagation();
},
onBlur: e => this.foundation.handleInputBlur(e)
}, selectInputProps));
}
renderDropdownInput() {
const {
size,
multiple,
disabled,
inputProps,
filter,
searchPosition,
searchPlaceholder
} = this.props;
const {
inputValue,
focusIndex
} = this.state;
const wrapperCls = (0, _classnames.default)(`${prefixcls}-dropdown-search-wrapper`, {});
const inputPropsCls = (0, _get2.default)(inputProps, 'className');
const inputCls = (0, _classnames.default)(`${prefixcls}-dropdown-input`, {
[`${prefixcls}-dropdown-input-single`]: !multiple,
[`${prefixcls}-dropdown-input-multiple`]: multiple
}, inputPropsCls);
const selectInputProps = Object.assign(Object.assign({
value: inputValue,
disabled,
className: inputCls,
onChange: this.handleInputChange,
placeholder: searchPlaceholder,
showClear: true
}, inputProps), {
/**
* When searchPosition is trigger, the keyboard events are bound to the outer trigger div, so there is no need to listen in input.
* When searchPosition is dropdown, the popup and the outer trigger div are not parent- child relationships,
* and bubbles cannot occur, so onKeydown needs to be listened in input.
* */
onKeyDown: e => this.foundation._handleKeyDown(e)
});
return /*#__PURE__*/_react.default.createElement("div", {
className: wrapperCls
}, /*#__PURE__*/_react.default.createElement(_index5.default, Object.assign({
ref: this.dropdownInputRef,
prefix: /*#__PURE__*/_react.default.createElement(_semiIcons.IconSearch, null),
"aria-activedescendant": focusIndex !== -1 ? `${this.selectID}-option-${focusIndex}` : ''
}, selectInputProps)));
}
close() {
this.foundation.close();
}
open() {
this.foundation.open();
}
clearInput() {
this.foundation.clearInput();
}
selectAll() {
this.foundation.selectAll();
}
deselectAll() {
this.foundation.clearSelected();
}
focus() {
this.foundation.focus();
}
onSelect(option, optionIndex, e) {
this.foundation.onSelect(option, optionIndex, e);
}
onClear(e) {
e.nativeEvent.stopImmediatePropagation();
this.foundation.handleClearClick(e);
}
search(value, event) {
this.handleInputChange(value, event);
}
renderEmpty() {
return /*#__PURE__*/_react.default.createElement(_option.default, {
empty: true,
emptyContent: this.props.emptyContent
});
}
renderLoading() {
const loadingWrapperCls = `${prefixcls}-loading-wrapper`;
return /*#__PURE__*/_react.default.createElement("div", {
className: loadingWrapperCls
}, /*#__PURE__*/_react.default.createElement(_spin.default, null));
}
renderOption(option, optionIndex, style) {
const {
focusIndex,
inputValue
} = this.state;
const {
renderOptionItem
} = this.props;
let optionContent;
const isFocused = optionIndex === focusIndex;
let optionStyle = style || {};
if (option.style) {
optionStyle = Object.assign(Object.assign({}, optionStyle), option.style);
}
if (option._inputCreateOnly) {
optionContent = this.renderCreateOption(option, isFocused, optionIndex, style);
} else {
// use another name to make sure that 'key' in optionList still exist when we call onChange
if ('key' in option) {
option._keyInOptionList = option.key;
}
optionContent = /*#__PURE__*/_react.default.createElement(_option.default, Object.assign({
showTick: true
}, option, {
selected: option._selected,
onSelect: (v, e) => this.onSelect(v, optionIndex, e),
focused: isFocused,
onMouseEnter: () => this.onOptionHover(optionIndex),
style: optionStyle,
key: option._keyInOptionList || option._keyInJsx || option.label + option.value + optionIndex,
renderOptionItem: renderOptionItem,
inputValue: inputValue,
semiOptionId: `${this.selectID}-option-${optionIndex}`
}), option.label);
}
return optionContent;
}
renderCreateOption(option, isFocused, optionIndex, style) {
const {
renderCreateItem
} = this.props;
// default render method
if (typeof renderCreateItem === 'undefined') {
const defaultCreateItem = /*#__PURE__*/_react.default.createElement(_option.default, Object.assign({
key: option.key || option.label + option.value,
onSelect: (v, e) => this.onSelect(v, optionIndex, e),
onMouseEnter: () => this.onOptionHover(optionIndex),
showTick: true
}, option, {
focused: isFocused,
style: style
}), /*#__PURE__*/_react.default.createElement(_localeConsumer.default, {
componentName: "Select"
}, locale => (/*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
className: `${prefixcls}-create-tips`
}, locale.createText), option.value))));
return defaultCreateItem;
}
const customCreateItem = renderCreateItem(option.value, isFocused, style);
return (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/interactive-supports-focus
_react.default.createElement("div", {
role: "button",
"aria-label": "Use the input box to create an optional item",
onClick: e => this.onSelect(option, optionIndex, e),
key: option.key || option.label
}, customCreateItem)
);
}
onOptionHover(optionIndex) {
this.foundation.handleOptionMouseEnter(optionIndex);
}
renderWithGroup(visibleOptions) {
const content = [];
const groupStatus = new Map();
visibleOptions.forEach((option, optionIndex) => {
const parentGroup = option._parentGroup;
const optionContent = this.renderOption(option, optionIndex);
if (parentGroup && !groupStatus.has(parentGroup.label)) {
const groupKey = typeof parentGroup.label === 'string' || typeof parentGroup.label === 'number' ? parentGroup.label : parentGroup.key;
// when use with OptionGroup and group content not already insert
const groupContent = /*#__PURE__*/_react.default.createElement(_optionGroup.default, Object.assign({}, parentGroup, {
key: groupKey
}));
groupStatus.set(parentGroup.label, true);
content.push(groupContent);
}
content.push(optionContent);
});
return content;
}
renderVirtualizeList(visibleOptions) {
const {
virtualize
} = this.props;
const {
direction
} = this.context;
const {
height,
width,
itemSize
} = virtualize;
return /*#__PURE__*/_react.default.createElement(_reactWindow.FixedSizeList, {
ref: this.virtualizeListRef,
height: height || _constants.numbers.LIST_HEIGHT,
itemCount: visibleOptions.length,
itemSize: itemSize,
itemData: {
visibleOptions,
renderOption: this.renderOption
},
width: width || '100%',
style: {
direction
}
}, _virtualRow.default);
}
renderOptions(children) {
const {
dropdownMinWidth,
options,
selections
} = this.state;
const {
maxHeight,
dropdownClassName,
dropdownStyle,
outerTopSlot,
innerTopSlot,
outerBottomSlot,
innerBottomSlot,
loading,
virtualize,
multiple,
emptyContent,
searchPosition,
filter
} = this.props;
// Do a filter first, instead of directly judging in forEach, so that the focusIndex can correspond to
const visibleOptions = options.filter(item => item._show);
let listContent = this.renderWithGroup(visibleOptions);
if (virtualize) {
listContent = this.renderVirtualizeList(visibleOptions);
}
const style = Object.assign({
minWidth: dropdownMinWidth
}, dropdownStyle);
const optionListCls = (0, _classnames.default)({
[`${prefixcls}-option-list`]: true,
[`${prefixcls}-option-list-chosen`]: selections.size
});
const isEmpty = !options.length || !options.some(item => item._show);
return (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
_react.default.createElement("div", {
id: `${prefixcls}-${this.selectOptionListID}`,
className: (0, _classnames.default)({
// When emptyContent is null and the option is empty, there is no need for the drop-down option for the user,
// so there is no need to set padding through this className
[`${prefixcls}-option-list-wrapper`]: !(isEmpty && emptyContent === null)
}, dropdownClassName),
style: style,
ref: this.setOptionContainerEl,
onKeyDown: e => this.foundation.handleContainerKeyDown(e)
}, outerTopSlot ? /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-option-list-outer-top-slot`,
onMouseEnter: () => this.foundation.handleSlotMouseEnter()
}, outerTopSlot) : null, searchPosition === _constants.strings.SEARCH_POSITION_DROPDOWN && filter ? this.renderDropdownInput() : null, /*#__PURE__*/_react.default.createElement("div", {
style: {
maxHeight: `${maxHeight}px`
},
className: optionListCls,
role: "listbox",
"aria-multiselectable": multiple,
onScroll: e => this.foundation.handleListScroll(e)
}, innerTopSlot ? /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-option-list-inner-top-slot`,
onMouseEnter: () => this.foundation.handleSlotMouseEnter()
}, innerTopSlot) : null, loading ? this.renderLoading() : isEmpty ? this.renderEmpty() : listContent, innerBottomSlot ? /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-option-list-inner-bottom-slot`,
onMouseEnter: () => this.foundation.handleSlotMouseEnter()
}, innerBottomSlot) : null), outerBottomSlot ? /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-option-list-outer-bottom-slot`,
onMouseEnter: () => this.foundation.handleSlotMouseEnter()
}, outerBottomSlot) : null)
);
}
renderSingleSelection(selections, filterable) {
let {
renderSelectedItem,
searchPosition
} = this.props;
const {
placeholder
} = this.props;
const {
showInput,
inputValue
} = this.state;
let renderText = '';
const selectedItems = [...selections];
if (typeof renderSelectedItem === 'undefined') {
renderSelectedItem = optionNode => optionNode.label;
}
if (selectedItems.length) {
const selectedItem = selectedItems[0][1];
renderText = renderSelectedItem(selectedItem);
}
const showInputInTrigger = searchPosition === _constants.strings.SEARCH_POSITION_TRIGGER;
const spanCls = (0, _classnames.default)({
[`${prefixcls}-selection-text`]: true,
[`${prefixcls}-selection-placeholder`]: !renderText && renderText !== 0,
[`${prefixcls}-selection-text-hide`]: inputValue && showInput && showInputInTrigger,
[`${prefixcls}-selection-text-inactive`]: !inputValue && showInput && showInputInTrigger // Stack Input & RenderText(opacity 0.4)
});
const contentWrapperCls = `${prefixcls}-content-wrapper`;
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: contentWrapperCls
}, /*#__PURE__*/_react.default.createElement("span", {
className: spanCls,
"x-semi-prop": "placeholder"
}, renderText || renderText === 0 ? renderText : placeholder), filterable && showInput && showInputInTrigger ? this.renderTriggerInput() : null));
}
renderTag(item, i, isCollapseItem) {
const {
size,
disabled: selectDisabled
} = this.props;
let {
renderSelectedItem
} = this.props;
const label = item[0];
const {
value
} = item[1];
const disabled = item[1].disabled || selectDisabled;
const onClose = (tagContent, e) => {
if (e && typeof e.preventDefault === 'function') {
e.preventDefault(); // make sure that tag will not hidden immediately in controlled mode
}
this.foundation.removeTag({
label,
value
});
};
if (typeof renderSelectedItem === 'undefined') {
renderSelectedItem = optionNode => ({
isRenderInTag: true,
content: optionNode.label
});
}
const {
content,
isRenderInTag
} = renderSelectedItem(item[1], {
index: i,
disabled,
onClose
});
const basic = {
disabled,
closable: !disabled,
onClose
};
const realContent = isCollapseItem && !(0, _isFunction2.default)(this.props.renderSelectedItem) ? (/*#__PURE__*/_react.default.createElement(_text.default, {
size: 'small',
ellipsis: {
rows: 1,
showTooltip: {
type: 'popover',
opts: {
style: {
width: 'auto',
fontSize: 12
}
}
}
}
}, content)) : content;
if (isRenderInTag) {
return /*#__PURE__*/_react.default.createElement(_index.default, Object.assign({}, basic, {
color: "white",
size: size || 'large',
key: value,
style: {
maxWidth: '100%'
}
}), realContent);
} else {
return /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: value
}, realContent);
}
}
renderNTag(n, restTags) {
const {
size,
showRestTagsPopover,
restTagsPopoverProps
} = this.props;
let nTag = /*#__PURE__*/_react.default.createElement(_index.default, {
closable: false,
size: size || 'large',
color: 'grey',
className: `${prefixcls}-content-wrapper-collapse-tag`,
key: `_+${n}`,
style: {
marginRight: 0,
flexShrink: 0
}
}, "+", n);
if (showRestTagsPopover) {
nTag = /*#__PURE__*/_react.default.createElement(_index4.default, Object.assign({
showArrow: true,
content: /*#__PURE__*/_react.default.createElement(_index3.default, {
spacing: 2,
wrap: true,
style: {
maxWidth: '400px'
}
}, restTags.map((tag, index) => this.renderTag(tag, index))),
trigger: "hover",
position: "top",
autoAdjustOverflow: true
}, restTagsPopoverProps, {
key: `_+${n}_Popover`
}), nTag);
}
return nTag;
}
renderOverflow(items, index) {
const isCollapse = true;
return items.length && items[0] ? this.renderTag(items[0], index, isCollapse) : null;
}
handleOverflow(items) {
const {
overflowItemCount,
selections
} = this.state;
const {
maxTagCount
} = this.props;
const newOverFlowItemCount = selections.size - maxTagCount > 0 ? selections.size - maxTagCount + items.length - 1 : items.length - 1;
if (overflowItemCount !== newOverFlowItemCount) {
this.foundation.updateOverflowItemCount(selections.size, newOverFlowItemCount);
}
}
renderCollapsedTags(selections, length) {
const {
overflowItemCount
} = this.state;
const normalTags = typeof length === 'number' ? selections.slice(0, length) : selections;
return /*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-content-wrapper-collapse`
}, /*#__PURE__*/_react.default.createElement(_index2.default, {
items: normalTags,
key: String(selections.length),
overflowRenderer: overflowItems => this.renderOverflow(overflowItems, length - 1),
onOverflow: overflowItems => this.handleOverflow(overflowItems),
visibleItemRenderer: (item, index) => this.renderTag(item, index)
}), overflowItemCount > 0 && this.renderNTag(overflowItemCount, selections.slice(selections.length - overflowItemCount)));
}
renderOneLineTags(selectedItems, n) {
let {
renderSelectedItem
} = this.props;
const {
showRestTagsPopover,
restTagsPopoverProps,
maxTagCount
} = this.props;
const {
isFullTags
} = this.state;
let tagContent;
if (typeof renderSelectedItem === 'undefined') {
renderSelectedItem = optionNode => ({
isRenderInTag: true,
content: optionNode.label
});
}
if (showRestTagsPopover) {
// showRestTagsPopover = true,
const mapItems = isFullTags ? selectedItems : selectedItems.slice(0, maxTagCount);
const tags = mapItems.map((item, i) => {
return this.getTagItem(item, i, renderSelectedItem);
});
tagContent = /*#__PURE__*/_react.default.createElement(_group.default, {
tagList: tags,
maxTagCount: n,
restCount: isFullTags ? undefined : selectedItems.length - maxTagCount,
size: "large",
mode: "custom",
showPopover: showRestTagsPopover,
popoverProps: restTagsPopoverProps,
onPlusNMouseEnter: () => {
this.foundation.updateIsFullTags();
}
});
} else {
// If maxTagCount is set, showRestTagsPopover is false/undefined,
// then there is no popover when hovering, no extra Tags are displayed,
// only the tags and restCount displayed in the trigger need to be passed in
const mapItems = selectedItems.slice(0, maxTagCount);
const tags = mapItems.map((item, i) => {
return this.getTagItem(item, i, renderSelectedItem);
});
tagContent = /*#__PURE__*/_react.default.createElement(_group.default, {
tagList: tags,
maxTagCount: n,
restCount: selectedItems.length - maxTagCount,
size: "large",
mode: "custom"
});
}
return tagContent;
}
renderMultipleSelection(selections, filterable) {
let {
renderSelectedItem,
searchPosition
} = this.props;
const {
placeholder,
maxTagCount,
expandRestTagsOnClick,
ellipsisTrigger
} = this.props;
const {
inputValue,
isOpen
} = this.state;
const selectedItems = [...selections];
if (typeof renderSelectedItem === 'undefined') {
renderSelectedItem = optionNode => ({
isRenderInTag: true,
content: optionNode.label
});
}
const contentWrapperCls = (0, _classnames.default)({
[`${prefixcls}-content-wrapper`]: true,
[`${prefixcls}-content-wrapper-one-line`]: maxTagCount && !isOpen,
[`${prefixcls}-content-wrapper-empty`]: !selectedItems.length
});
const spanCls = (0, _classnames.default)({
[`${prefixcls}-selection-text`]: true,
[`${prefixcls}-selection-placeholder`]: !selectedItems.length,
[`${prefixcls}-selection-text-hide`]: selectedItems && selectedItems.length
});
const placeholderText = placeholder && !inputValue ? /*#__PURE__*/_react.default.createElement("span", {
className: spanCls
}, placeholder) : null;
const n = selectedItems.length > maxTagCount ? maxTagCount : undefined;
const NotOneLine = !maxTagCount;
const oneLineTags = ellipsisTrigger ? this.renderCollapsedTags(selectedItems, n) : this.renderOneLineTags(selectedItems, n);
const tagContent = NotOneLine || expandRestTagsOnClick && isOpen ? selectedItems.map((item, i) => this.renderTag(item, i)) : oneLineTags;
const showTriggerInput = filterable && searchPosition === _constants.strings.SEARCH_POSITION_TRIGGER;
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: contentWrapperCls
}, selectedItems && selectedItems.length ? tagContent : placeholderText, showTriggerInput ? this.renderTriggerInput() : null));
}
onMouseEnter(e) {
this.foundation.handleMouseEnter(e);
}
onMouseLeave(e) {
this.foundation.handleMouseLeave(e);
}
onKeyPress(e) {
this.foundation.handleKeyPress(e);
}
/* Processing logic when popover visible changes */
handlePopoverVisibleChange(status) {
const {
virtualize
} = this.props;
const {
selections
} = this.state;
if (!status) {
return;
}
if (virtualize) {
let minItemIndex = -1;
selections.forEach(item => {
const itemIndex = (0, _get2.default)(item, '_scrollIndex');
/* When the itemIndex is legal */
if ((0, _isNumber2.default)(itemIndex) && itemIndex >= 0) {
minItemIndex = minItemIndex !== -1 && minItemIndex < itemIndex ? minItemIndex : itemIndex;
}
});
if (minItemIndex !== -1) {
try {
this.virtualizeListRef.current.scrollToItem(minItemIndex, 'center');
} catch (error) {}
}
} else {
this.foundation.updateScrollTop();
}
}
renderSuffix() {
const {
suffix
} = this.props;
const suffixWrapperCls = (0, _classnames.default)({
[`${prefixcls}-suffix`]: true,
[`${prefixcls}-suffix-text`]: suffix && (0, _isString2.default)(suffix),
[`${prefixcls}-suffix-icon`]: (0, _utils2.isSemiIcon)(suffix)
});
return /*#__PURE__*/_react.default.createElement("div", {
className: suffixWrapperCls,
"x-semi-prop": "suffix"
}, suffix);
}
renderPrefix() {
const {
prefix,
insetLabel,
insetLabelId
} = this.props;
const labelNode = prefix || insetLabel;
const prefixWrapperCls = (0, _classnames.default)({
[`${prefixcls}-prefix`]: true,
[`${prefixcls}-inset-label`]: insetLabel,
[`${prefixcls}-prefix-text`]: labelNode && (0, _isString2.default)(labelNode),
[`${prefixcls}-prefix-icon`]: (0, _utils2.isSemiIcon)(labelNode)
});
return /*#__PURE__*/_react.default.createElement("div", {
className: prefixWrapperCls,
id: insetLabelId,
"x-semi-prop": "prefix,insetLabel"
}, labelNode);
}
renderSelection() {
const _a = this.props,
{
disabled,
multiple,
filter,
style,
id,
size,
className,
validateStatus,
showArrow,
suffix,
prefix,
insetLabel,
placeholder,
triggerRender,
arrowIcon,
clearIcon,
borderless
} = _a,
rest = __rest(_a, ["disabled", "multiple", "filter", "style", "id", "size", "className", "validateStatus", "showArrow", "suffix", "prefix", "insetLabel", "placeholder", "triggerRender", "arrowIcon", "clearIcon", "borderless"]);
const {
selections,
isOpen,
keyboardEventSet,
inputValue,
isHovering,
isFocus,
showInput,
focusIndex
} = this.state;
const useCustomTrigger = typeof triggerRender === 'function';
const filterable = Boolean(filter); // filter(boolean || function)
const selectionCls = useCustomTrigger ? (0, _classnames.default)(className) : (0, _classnames.default)(prefixcls, className, {
[`${prefixcls}-borderless`]: borderless,
[`${prefixcls}-open`]: isOpen,
[`${prefixcls}-focus`]: isFocus,
[`${prefixcls}-disabled`]: disabled,
[`${prefixcls}-single`]: !multiple,
[`${prefixcls}-multiple`]: multiple,
[`${prefixcls}-filterable`]: filterable,
[`${prefixcls}-small`]: size === 'small',
[`${prefixcls}-large`]: size === 'large',
[`${prefixcls}-error`]: validateStatus === 'error',
[`${prefixcls}-warning`]: validateStatus === 'warning',
[`${prefixcls}-no-arrow`]: !showArrow,
[`${prefixcls}-with-prefix`]: prefix || insetLabel,
[`${prefixcls}-with-suffix`]: suffix
});
const showClear = this.props.showClear && (selections.size || inputValue) && !disabled && (isHovering || isOpen);
const arrowContent = showArrow ? (/*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-arrow`,
"x-semi-prop": "arrowIcon"
}, arrowIcon)) : (/*#__PURE__*/_react.default.createElement("div", {
className: `${prefixcls}-arrow-empty`
}));
const clear = clearIcon ? clearIcon : /*#__PURE__*/_react.default.createElement(_semiIcons.IconClear, null);
// semantics of onSearch are more in line with behavior, onChange is alias of onSearch, will be deprecate next major version
const inner = useCustomTrigger ? (/*#__PURE__*/_react.default.createElement(_trigger.default, {
value: Array.from(selections.values()),
inputValue: inputValue,
onChange: this.handleInputChange,
onSearch: this.handleInputChange,
onRemove: item => this.foundation.removeTag(item),
onClear: this.onClear,
disabled: disabled,
triggerRender: triggerRender,
placeholder: placeholder,
componentName: "Select",
componentProps: Object.assign({}, this.props)
})) : [/*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: "prefix"
}, prefix || insetLabel ? this.renderPrefix() : null), /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: "selection"
}, /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(`${prefixcls}-selection`)
}, multiple ? this.renderMultipleSelection(selections, filterable) : this.renderSingleSelection(selections, filterable))), /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: "suffix"
}, suffix ? this.renderSuffix() : null), /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: "clearicon"
}, showClear ? (/*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(`${prefixcls}-clear`),
onClick: this.onClear
}, clear)) : arrowContent)];
/**
*
* In disabled, searchable single-selection and display input, and searchable multi-selection
* make combobox not focusable by tab key
*
* 在disabled,可搜索单选且显示input框,以及可搜索多选情况下
* 让combobox无法通过tab聚焦
*/
const tabIndex = disabled || filterable && showInput || filterable && multiple ? -1 : 0;
return (
/*#__PURE__*/
/* eslint-disable-next-line jsx-a11y/aria-activedescendant-has-tabindex */
_react.default.createElement("div", Object.assign({
role: "combobox",
"aria-disabled": disabled,
"aria-expanded": isOpen,
"aria-controls": `${prefixcls}-${this.selectOptionListID}`,
"aria-haspopup": "listbox",
"aria-label": selections.size ? 'selected' : '',
"aria-invalid": this.props['aria-invalid'],
"aria-errormessage": this.props['aria-errormessage'],
"aria-labelledby": this.props['aria-labelledby'],
"aria-describedby": this.props['aria-describedby'],
"aria-required": this.props['aria-required'],
className: selectionCls,
ref: ref => this.triggerRef.current = ref,
onClick: e => this.foundation.handleClick(e),
style: style,
id: this.selectID,
tabIndex: tabIndex,
"aria-activedescendant": focusIndex !== -1 ? `${this.selectID}-option-${focusIndex}` : '',
onMouseEnter: this.onMouseEnter,
onMouseLeave: this.onMouseLeave,
onFocus: e => this.foundation.handleTriggerFocus(e),
onBlur: e => this.foundation.handleTriggerBlur(e),
onKeyPress: this.onKeyPress
}, keyboardEventSet, this.getDataAttr(rest)), inner)
);
}
render() {
const {
direction
} = this.context;
const defaultPosition = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
const {
children,
position = defaultPosition,
zIndex,
getPopupContainer,
motion,
autoAdjustOverflow,
mouseLeaveDelay,
mouseEnterDelay,
spacing,
stopPropagation,
dropdownMargin
} = this.props;
const {
isOpen,
optionKey
} = this.state;
const selection = this.renderSelection();
return /*#__PURE__*/_react.default.createElement(_index4.default, {
getPopupContainer: getPopupContainer,
motion: motion,
margin: dropdownMargin,
autoAdjustOverflow: autoAdjustOverflow,
mouseLeaveDelay: mouseLeaveDelay,
mouseEnterDelay: mouseEnterDelay,
zIndex: zIndex,
ref: this.optionsRef,
content: () => this.renderOptions(children),
visible: isOpen,
trigger: "custom",
rePosKey: optionKey,
position: position,
spacing: spacing,
stopPropagation: stopPropagation,
disableArrowKeyDown: true,
onVisibleChange: status => this.handlePopoverVisibleChange(status),
afterClose: () => this.foundation.handlePopoverClose()
}, selection);
}
}
Select.contextType = _context.default;
Select.Option = _option.default;
Select.OptGroup = _optionGroup.default;
Select.propTypes = {
'aria-describedby': _propTypes.default.string,
'aria-errormessage': _propTypes.default.string,
'aria-invalid': _propTypes.default.bool,
'aria-labelledby': _propTypes.default.string,
'aria-required': _propTypes.default.bool,
autoFocus: _propTypes.default.bool,
autoClearSearchValue: _propTypes.default.bool,
borderless: _propTypes.default.bool,
children: _propTypes.default.node,
clearIcon: _propTypes.default.node,
defaultValue: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number, _propTypes.default.array, _propTypes.default.object]),
ellipsisTrigger: _propTypes.default.bool,
value: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number, _propTypes.default.array, _propTypes.default.object]),
placeholder: _propTypes.default.node,
onChange: _propTypes.default.func,
multiple: _propTypes.default.bool,
// Whether to turn on the input box filtering function, when it is a function, it represents a custom filtering function
filter: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.bool]),
// How many tags can you choose?
max: _propTypes.default.number,
// How many tabs are displayed at most, and the rest are displayed in + N
maxTagCount: _propTypes.default.number,
maxHeight: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
style: _propTypes.default.object,
className: _propTypes.default.string,
size: _propTypes.default.oneOf(_constants.strings.SIZE_SET),
disabled: _propTypes.default.bool,
emptyContent: _propTypes.default.node,
expandRestTagsOnClick: _propTypes.default.bool,
onDropdownVisibleChange: _propTypes.default.func,
zIndex: _propTypes.default.number,
position: _propTypes.default.oneOf(_constants.strings.POSITION_SET),
onSearch: _propTypes.default.func,
getPopupContainer: _propTypes.default.func,
dropdownClassName: _propTypes.default.string,
dropdownStyle: _propTypes.default.object,
dropdownMargin: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.object]),
outerTopSlot: _propTypes.default.node,
innerTopSlot: _propTypes.default.node,
inputProps: _propTypes.default.object,
outerBottomSlot: _propTypes.default.node,
innerBottomSlot: _propTypes.default.node,
optionList: _propTypes.default.array,
dropdownMatchSelectWidth: _propTypes.default.bool,
loading: _propTypes.default.bool,
defaultOpen: _propTypes.default.bool,
validateStatus: _propTypes.default.oneOf(_constants.strings.STATUS),
defaultActiveFirstOption: _propTypes.default.bool,
triggerRender: _propTypes.default.func,
stopPropagation: _propTypes.default.bool,
searchPosition: _propTypes.default.string,
// motion doesn't need to be exposed
motion: _propTypes.default.bool,
onChangeWithObject: _propTypes.default.bool,
suffix: _propTypes.default.node,
prefix: _propTypes.default.node,
insetLabel: _propTypes.default.node,
insetLabelId: _propTypes.default.string,
showClear: _propTypes.default.bool,
showArrow: _propTypes.default.bool,
renderSelectedI