@wix/design-system
Version:
@wix/design-system
193 lines • 7.2 kB
JavaScript
import React from 'react';
import classNames from 'classnames';
import InputWithOptions from '../InputWithOptions';
import InputWithTags from './InputWithTags';
import last from 'lodash/last';
import difference from 'difference';
import { st, classes } from './MultiSelect.st.css.js';
class MultiSelect extends InputWithOptions {
constructor(props) {
super(props);
this.onKeyDown = this.onKeyDown.bind(this);
this.onPaste = this.onPaste.bind(this);
this._onBlur = this._onBlur.bind(this);
this.state = { ...this.state, pasteDetected: false };
}
hideOptions() {
super.hideOptions();
if (this.props.clearOnBlur) {
this.clearInput();
}
}
rootAdditionalProps() {
const { className } = this.props;
return {
className: st(classes.root, className),
};
}
onClickOutside() {
if (this.state.showOptions) {
this.hideOptions();
}
}
_onBlur(event) {
super._onBlur(event);
this.props.acceptOnBlur && this.submitValue(this.state.inputValue);
}
getUnselectedOptions() {
const optionIds = this.props.options.map(option => option.id);
const tagIds = this.props.tags.map(tag => tag.id);
const unselectedOptionsIds = difference(optionIds, tagIds);
return this.props.options.filter(option => unselectedOptionsIds.includes(option.id));
}
dropdownAdditionalProps() {
const { predicate, emptyStateMessage, fixedFooter } = this.props;
const filterFunc = this.state.isEditing ? predicate : () => true;
const filtered = this.getUnselectedOptions().filter(filterFunc);
let options = filtered;
if (emptyStateMessage && filtered.length === 0) {
options = [
{
id: 'empty-state-message',
value: emptyStateMessage,
disabled: true,
},
];
}
return {
options,
closeOnSelect: false,
selectedHighlight: false,
selectedId: -1,
fixedFooter,
};
}
closeOnSelect() {
return false;
}
isDropdownLayoutVisible() {
// Don't show dropdown if there are no options available
const options = this.props.options || [];
const isWithOtherItems = !!this.props.fixedFooter ||
!!this.props.emptyStateMessage ||
!!this.props.customDropdownContent;
return (super.isDropdownLayoutVisible() &&
(options.length > 0 || isWithOtherItems));
}
inputAdditionalProps() {
return {
readOnly: this.props.readOnly,
disableEditing: true,
inputElement: (React.createElement(InputWithTags, { className: classes.inputWithTags, onReorder: this.props.onReorder, maxNumRows: this.props.maxNumRows, mode: this.props.mode, hideCustomSuffix: this.isDropdownLayoutVisible(), customSuffix: this.props.customSuffix, customPrefix: this.props.customPrefix, border: this.props.border })),
onKeyDown: this.onKeyDown,
delimiters: this.props.delimiters,
onPaste: this.onPaste,
};
}
onPaste() {
this.setState({ pasteDetected: true });
}
_splitByDelimitersAndTrim(value) {
const delimitersRegexp = new RegExp(this.props.delimiters.join('|'), 'g');
return value
.split(delimitersRegexp)
.map(str => str.trim())
.filter(str => str);
}
_onChange(event) {
if (this.state.pasteDetected) {
const value = event.target.value;
this.setState({ pasteDetected: false }, () => {
this.submitValue(value);
});
}
else {
this.setState({ inputValue: event.target.value });
this.props.onChange && this.props.onChange(event);
}
// If the input value is not empty, should show the options
if (event.target.value.trim()) {
this.showOptions();
}
}
_onSelect(option) {
this.onSelect(option);
}
_onManuallyInput(inputValue, event) {
const { value } = this.props;
// FIXME: InputWithOptions is not updating it's inputValue state when the `value` prop changes.
// So using `value` here, covers for that bug. (This is tested)
// BTW: Previously, `value` was used to trigger onSelect, and `inputValue` was used to trigger onManuallyInput. Which is crazy.
// So now both of them trigger a submit (onManuallyInput).
const _value = (value && value.trim()) || (inputValue && inputValue.trim());
this.submitValue(_value);
_value && event.preventDefault();
if (this.closeOnSelect()) {
this.hideOptions();
}
}
getManualSubmitKeys() {
return ['Enter', 'Tab'].concat(this.props.delimiters);
}
onKeyDown(event) {
const { tags, value, onRemoveTag } = this.props;
if (tags.length > 0 &&
(event.key === 'Delete' || event.key === 'Backspace') &&
value &&
value.length === 0) {
onRemoveTag(last(tags).id);
}
if (event.key === 'Escape') {
this.clearInput();
super.hideOptions();
event.stopPropagation();
}
if (this.props.onKeyDown) {
this.props.onKeyDown(event);
}
}
optionToTag({ id, value, tag, theme }) {
return tag ? { id, ...tag } : { id, label: value, theme };
}
onSelect(option) {
this.clearInput();
const { onSelect } = this.props;
if (onSelect) {
onSelect(this.props.options.find(o => o.id === option.id));
}
}
submitValue(inputValue) {
if (!inputValue) {
return;
}
const { onManuallyInput } = this.props;
const values = this._splitByDelimitersAndTrim(inputValue);
onManuallyInput && values.length && onManuallyInput(values);
this.clearInput();
}
clearInput() {
this.input.current && this.input.current.clear();
if (this.props.onChange) {
this.props.onChange({ target: { value: '' } });
}
}
}
MultiSelect.autoSizeInput = ({ className, 'data-ref': dataRef, ...rest }) => {
const inputClassName = classNames(className, classes.autoSizeInput);
return React.createElement("input", { ...rest, ref: dataRef, className: inputClassName });
};
MultiSelect.autoSizeInputWithRef = () => React.forwardRef((props, ref) => (({ className, ref, ...rest }) => {
const inputClassName = classNames(className, classes.autoSizeInput);
return React.createElement("input", { ...rest, ref: ref, className: inputClassName });
})({ ...props, ref }));
MultiSelect.displayName = 'MultiSelect';
MultiSelect.defaultProps = {
...InputWithOptions.defaultProps,
predicate: () => true,
tags: [],
delimiters: [','],
clearOnBlur: true,
customInput: MultiSelect.autoSizeInputWithRef(),
};
export default MultiSelect;
//# sourceMappingURL=MultiSelect.js.map