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.

449 lines (448 loc) 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _noop2 = _interopRequireDefault(require("lodash/noop")); var _isEqual2 = _interopRequireDefault(require("lodash/isEqual")); var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _classnames = _interopRequireDefault(require("classnames")); var _constants = require("@douyinfe/semi-foundation/lib/cjs/autoComplete/constants"); var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/autoComplete/foundation")); var _constants2 = require("@douyinfe/semi-foundation/lib/cjs/popover/constants"); var _uuid = require("@douyinfe/semi-foundation/lib/cjs/utils/uuid"); var _baseComponent = _interopRequireDefault(require("../_base/baseComponent")); var _spin = _interopRequireDefault(require("../spin")); var _popover = _interopRequireDefault(require("../popover")); var _input = _interopRequireDefault(require("../input")); var _trigger = _interopRequireDefault(require("../trigger")); var _option = _interopRequireDefault(require("./option")); var _warning = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/warning")); require("@douyinfe/semi-foundation/lib/cjs/autoComplete/autoComplete.css"); var _reactDom = _interopRequireDefault(require("react-dom")); var _utils = require("../_utils"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const prefixCls = _constants.cssClasses.PREFIX; const sizeSet = _constants.strings.SIZE; const positionSet = _constants.strings.POSITION; const statusSet = _constants.strings.STATUS; class AutoComplete extends _baseComponent.default { constructor(props) { super(props); this.onSelect = (option, optionIndex, e) => { this.foundation.handleSelect(option, optionIndex); }; this.onSearch = value => { this.foundation.handleSearch(value); }; this.onBlur = e => this.foundation.handleBlur(e); this.onFocus = e => this.foundation.handleFocus(e); this.onInputClear = () => this.foundation.handleClear(); this.handleInputClick = e => this.foundation.handleInputClick(e); this.foundation = new _foundation.default(this.adapter); const initRePosKey = 1; this.state = { dropdownMinWidth: null, inputValue: '', // option list options: [], // popover visible visible: false, // current focus option index focusIndex: props.defaultActiveFirstOption ? 0 : -1, // current selected options selection: new Map(), rePosKey: initRePosKey }; this.triggerRef = /*#__PURE__*/_react.default.createRef(); this.optionsRef = /*#__PURE__*/_react.default.createRef(); this.clickOutsideHandler = null; this.optionListId = ''; (0, _warning.default)('triggerRender' in this.props && typeof this.props.triggerRender === 'function', `[Semi AutoComplete] - If you are using the following props: 'suffix', 'prefix', 'showClear', 'validateStatus', and 'size', please notice that they will be removed in the next major version. Please use 'componentProps' to retrieve these props instead. - If you are using 'onBlur', 'onFocus', please try to avoid using them and look for changes in the future.`); } get adapter() { const keyboardAdapter = { registerKeyDown: cb => { const keyboardEventSet = { onKeyDown: cb }; this.setState({ keyboardEventSet }); }, unregisterKeyDown: cb => { this.setState({ keyboardEventSet: {} }); }, updateFocusIndex: focusIndex => { this.setState({ focusIndex }); }, updateScrollTop: index => { let optionClassName; /** * Unlike Select which needs to process renderOptionItem separately, when renderItem is enabled in autocomplete * the content passed by the user is still wrapped in the selector of .semi-autocomplete-option * so the selector does not need to be judged separately. */ optionClassName = `.${prefixCls}-option-selected`; if (index !== undefined) { optionClassName = `.${prefixCls}-option:nth-child(${index + 1})`; } let destNode = document.querySelector(`#${prefixCls}-${this.optionListId} ${optionClassName}`); if (Array.isArray(destNode)) { destNode = destNode[0]; } if (destNode) { const destParent = destNode.parentNode; destParent.scrollTop = destNode.offsetTop - destParent.offsetTop - destParent.clientHeight / 2 + destNode.clientHeight / 2; } } }; return Object.assign(Object.assign(Object.assign({}, super.adapter), keyboardAdapter), { getTriggerWidth: () => { const el = this.triggerRef.current; return el && el.getBoundingClientRect().width; }, setOptionWrapperWidth: width => { this.setState({ dropdownMinWidth: width }); }, updateInputValue: inputValue => { this.setState({ inputValue }); }, toggleListVisible: isShow => { this.setState({ visible: isShow }); }, updateOptionList: optionList => { this.setState({ options: optionList }); }, updateSelection: selection => { this.setState({ selection }); }, notifySearch: inputValue => { this.props.onSearch(inputValue); }, notifyChange: value => { this.props.onChange(value); }, notifySelect: option => { this.props.onSelect(option); }, notifyDropdownVisibleChange: isVisible => { this.props.onDropdownVisibleChange(isVisible); }, notifyClear: () => { this.props.onClear(); }, notifyFocus: event => { this.props.onFocus(event); }, notifyBlur: event => { this.props.onBlur(event); }, notifyKeyDown: e => { this.props.onKeyDown(e); }, rePositionDropdown: () => { let { rePosKey } = this.state; rePosKey = rePosKey + 1; this.setState({ rePosKey }); }, 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) || !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: () => { if (this.clickOutsideHandler) { document.removeEventListener('mousedown', this.clickOutsideHandler, false); } } }); } componentDidMount() { this.foundation.init(); this.optionListId = (0, _uuid.getUuidShort)(); } componentWillUnmount() { this.foundation.destroy(); } componentDidUpdate(prevProps, prevState) { if (!(0, _isEqual2.default)(this.props.data, prevProps.data)) { this.foundation.handleDataChange(this.props.data); } if (this.props.value !== prevProps.value) { this.foundation.handleValueChange(this.props.value); } } renderInput() { const { size, prefix, insetLabel, insetLabelId, suffix, placeholder, style, className, showClear, disabled, triggerRender, validateStatus, autoFocus, value, id, clearIcon } = this.props; const { inputValue, keyboardEventSet, selection } = this.state; const useCustomTrigger = typeof triggerRender === 'function'; const outerProps = Object.assign(Object.assign(Object.assign({ style, className: useCustomTrigger ? (0, _classnames.default)(className) : (0, _classnames.default)({ [prefixCls]: true, [`${prefixCls}-disabled`]: disabled }, className), onClick: this.handleInputClick, ref: this.triggerRef, id }, keyboardEventSet), { // tooltip give tabindex 0 to children by default, autoComplete just need the input get focus, so outer div's tabindex set to -1 tabIndex: -1 }), this.getDataAttr(this.props)); const innerProps = { disabled, placeholder, autoFocus: autoFocus, onChange: this.onSearch, onClear: this.onInputClear, 'aria-label': this.props['aria-label'], 'aria-labelledby': this.props['aria-labelledby'], 'aria-invalid': this.props['aria-invalid'], 'aria-errormessage': this.props['aria-errormessage'], 'aria-describedby': this.props['aria-describedby'], 'aria-required': this.props['aria-required'], // TODO: remove in next major version suffix, prefix: prefix || insetLabel, insetLabelId, showClear, validateStatus, size, onBlur: this.onBlur, onFocus: this.onFocus, clearIcon }; return /*#__PURE__*/_react.default.createElement("div", Object.assign({}, outerProps), typeof triggerRender === 'function' ? (/*#__PURE__*/_react.default.createElement(_trigger.default, Object.assign({}, innerProps, { inputValue: typeof value !== 'undefined' ? value : inputValue, value: Array.from(selection.values()), triggerRender: triggerRender, componentName: "AutoComplete", componentProps: Object.assign({}, this.props) }))) : (/*#__PURE__*/_react.default.createElement(_input.default, Object.assign({}, innerProps, { value: typeof value !== 'undefined' ? value : inputValue })))); } renderLoading() { const loadingWrapperCls = `${prefixCls}-loading-wrapper`; return /*#__PURE__*/_react.default.createElement("div", { className: loadingWrapperCls }, /*#__PURE__*/_react.default.createElement(_spin.default, null)); } renderOption(option, optionIndex) { const { focusIndex } = this.state; const isFocused = optionIndex === focusIndex; return /*#__PURE__*/_react.default.createElement(_option.default, Object.assign({ showTick: false, onSelect: (v, e) => this.onSelect(v, optionIndex, e), // selected={selection.has(option.label)} focused: isFocused, onMouseEnter: () => this.foundation.handleOptionMouseEnter(optionIndex), key: option.key || option.label + option.value + optionIndex }, option), option.label); } renderOptionList() { const { maxHeight, dropdownStyle, dropdownClassName, loading, emptyContent } = this.props; const { options, dropdownMinWidth } = this.state; const listCls = (0, _classnames.default)({ [`${prefixCls}-option-list`]: true }, dropdownClassName); let optionsNode; if (options.length === 0) { optionsNode = emptyContent; } else { optionsNode = options.filter(option => option.show).map((option, i) => this.renderOption(option, i)); } const style = Object.assign({ maxHeight: maxHeight, minWidth: dropdownMinWidth }, dropdownStyle); return /*#__PURE__*/_react.default.createElement("div", { className: listCls, role: "listbox", style: style, id: `${prefixCls}-${this.optionListId}` }, !loading ? optionsNode : this.renderLoading()); } render() { const { position, motion, zIndex, mouseEnterDelay, mouseLeaveDelay, autoAdjustOverflow, stopPropagation, getPopupContainer } = this.props; const { visible, rePosKey } = this.state; const input = this.renderInput(); const optionList = this.renderOptionList(); return /*#__PURE__*/_react.default.createElement(_popover.default, { mouseEnterDelay: mouseEnterDelay, mouseLeaveDelay: mouseLeaveDelay, autoAdjustOverflow: autoAdjustOverflow, trigger: "custom", motion: motion, visible: visible, content: optionList, position: position, ref: this.optionsRef, // TransformFromCenter TODO: need to confirm zIndex: zIndex, stopPropagation: stopPropagation, getPopupContainer: getPopupContainer, rePosKey: rePosKey }, input); } } AutoComplete.propTypes = { 'aria-label': _propTypes.default.string, 'aria-labelledby': _propTypes.default.string, 'aria-invalid': _propTypes.default.bool, 'aria-errormessage': _propTypes.default.string, 'aria-describedby': _propTypes.default.string, 'aria-required': _propTypes.default.bool, autoFocus: _propTypes.default.bool, autoAdjustOverflow: _propTypes.default.bool, className: _propTypes.default.string, clearIcon: _propTypes.default.node, children: _propTypes.default.node, data: _propTypes.default.array, defaultOpen: _propTypes.default.bool, defaultValue: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), defaultActiveFirstOption: _propTypes.default.bool, disabled: _propTypes.default.bool, dropdownMatchSelectWidth: _propTypes.default.bool, dropdownClassName: _propTypes.default.string, dropdownStyle: _propTypes.default.object, emptyContent: _propTypes.default.node, id: _propTypes.default.string, insetLabel: _propTypes.default.node, insetLabelId: _propTypes.default.string, onSearch: _propTypes.default.func, onSelect: _propTypes.default.func, onClear: _propTypes.default.func, onBlur: _propTypes.default.func, onFocus: _propTypes.default.func, onChange: _propTypes.default.func, onKeyDown: _propTypes.default.func, position: _propTypes.default.oneOf(positionSet), placeholder: _propTypes.default.string, prefix: _propTypes.default.node, onChangeWithObject: _propTypes.default.bool, onSelectWithObject: _propTypes.default.bool, renderItem: _propTypes.default.func, renderSelectedItem: _propTypes.default.func, suffix: _propTypes.default.node, showClear: _propTypes.default.bool, size: _propTypes.default.oneOf(sizeSet), style: _propTypes.default.object, stopPropagation: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.string]), maxHeight: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), mouseEnterDelay: _propTypes.default.number, mouseLeaveDelay: _propTypes.default.number, motion: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.func, _propTypes.default.object]), getPopupContainer: _propTypes.default.func, triggerRender: _propTypes.default.func, value: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), validateStatus: _propTypes.default.oneOf(statusSet), zIndex: _propTypes.default.number }; AutoComplete.Option = _option.default; AutoComplete.__SemiComponentName__ = "AutoComplete"; AutoComplete.defaultProps = (0, _utils.getDefaultPropsFromGlobalConfig)(AutoComplete.__SemiComponentName__, { stopPropagation: true, motion: true, zIndex: _constants2.numbers.DEFAULT_Z_INDEX, position: 'bottomLeft', data: [], showClear: false, size: 'default', onFocus: _noop2.default, onSearch: _noop2.default, onClear: _noop2.default, onBlur: _noop2.default, onSelect: _noop2.default, onChange: _noop2.default, onSelectWithObject: false, onDropdownVisibleChange: _noop2.default, defaultActiveFirstOption: false, dropdownMatchSelectWidth: true, loading: false, maxHeight: 300, validateStatus: 'default', autoFocus: false, emptyContent: null, onKeyDown: _noop2.default // onPressEnter: () => undefined, // defaultOpen: false, }); var _default = exports.default = AutoComplete;