UNPKG

@wix/design-system

Version:

@wix/design-system

193 lines 7.2 kB
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