@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
JavaScript
"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;