UNPKG

@talend/react-components

Version:
329 lines (325 loc) 10.4 kB
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* eslint-disable react/sort-comp */ import { Component } from 'react'; import { withTranslation } from 'react-i18next'; import classnames from 'classnames'; import memoizeOne from 'memoize-one'; import PropTypes from 'prop-types'; import { ActionButton } from '../Actions'; import I18N_DOMAIN_COMPONENTS from '../constants'; import Icon from '../Icon'; import VirtualizedList from '../VirtualizedList'; import { CREATE_NEW_VALUE, SELECT_ALL_VALUE } from './constants'; import Dropdown from './Dropdown.container'; import { ItemOption } from './ItemOption.component'; import { ItemView } from './ItemView.component'; import theme from './MultiSelect.module.scss'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; function initSelectedMap(selected) { return selected.reduce((acc, current) => { acc.set(current, true); return acc; }, new Map()); } function isIn(element, container) { if (element.parentElement === null) { return false; } if (element.parentElement !== container) { return isIn(element.parentElement, container); } return true; } class MultiSelect extends Component { constructor(props) { super(props); this.state = { added: [] }; this.onSearchChange = this.onSearchChange.bind(this); this.onRowClick = this.onRowClick.bind(this); this.onInputFocus = this.onInputFocus.bind(this); this.onInputKeyDown = this.onInputKeyDown.bind(this); this.onClearAll = this.onClearAll.bind(this); this.closeOnOutsideClick = this.closeOnOutsideClick.bind(this); this.initSelectedMap = memoizeOne(initSelectedMap); } componentDidMount() { document.addEventListener('click', this.closeOnOutsideClick); } componentWillUnmount() { document.removeEventListener('click', this.closeOnOutsideClick); } closeOnOutsideClick(event) { if (this.containerRef !== null && !isIn(event.target, this.containerRef)) { this.setState({ showDropdown: false, searchTerm: '' }); } } onInputFocus(event) { this.setState({ showDropdown: true }); if (this.props.onFocus) { this.props.onFocus(event); } } onInputKeyDown(event) { if (event.key === 'Esc' || event.key === 'Escape') { event.preventDefault(); this.setState({ showDropdown: false }); } } onClearAll(event) { this.updateSelection(event, new Map()); } onSearchChange(event) { this.setState({ searchTerm: event.target.value }); } onRowClick(event, action) { switch (action) { case SELECT_ALL_VALUE: this.selectAll(event); break; case CREATE_NEW_VALUE: this.createNew(event); break; default: this.selectOne(event, action); } } getSelectedMap() { // uncontrolled if (this.state.selected) { return this.state.selected; } // controlled return this.initSelectedMap(this.props.selected); } getSelectedItems() { const selected = this.getSelectedMap(); return this.props.options.concat(this.state.added || []).filter(item => selected.get(item.value)); } getFilteredOptions() { const { added = [], searchTerm = '' } = this.state; const { options, restricted, t } = this.props; const baseOptions = options.concat(added); if (!searchTerm) { return baseOptions; } // filter base options let hasExactMatch = false; const lowerSearchTerm = searchTerm.toLowerCase(); const filteredOptions = baseOptions.filter(({ name }) => { const lowerOption = name.toLowerCase(); hasExactMatch = hasExactMatch || lowerOption === lowerSearchTerm; return name.toLowerCase().indexOf(lowerSearchTerm) !== -1; }); // insert new value creation option if (!restricted && !hasExactMatch) { filteredOptions.push({ value: CREATE_NEW_VALUE, name: t('MULTI_SELECT_LABEL_CREATE_NEW', { defaultValue: '{{name}} (Create entry)', name: searchTerm }) }); } return filteredOptions; } getListItems() { const { searchTerm } = this.state; const { t } = this.props; // apply search term on passed + added options const options = this.getFilteredOptions().map(item => ({ name: item.name, value: item.value, selected: this.getSelectedMap().get(item.value), searchTerm })); // insert select all option if (options.length) { const allSelected = options.every(item => item.selected); options.unshift({ value: SELECT_ALL_VALUE, name: allSelected ? t('MULTI_SELECT_LABEL_DESELECT_ALL', { defaultValue: 'Deselect all' }) : t('MULTI_SELECT_LABEL_SELECT_ALL', { defaultValue: 'Select all' }), selected: allSelected }); } return options; } createNew(event) { const newItem = { name: this.state.searchTerm, value: this.state.searchTerm }; const selected = new Map(this.getSelectedMap()); selected.set(newItem.value, true); this.updateSelection(event, selected); this.setState(({ added }) => ({ added: added.concat([newItem]) })); } selectAll(event) { // toggle the select only if all visible items are already selected const selected = this.getSelectedMap(); const options = this.getFilteredOptions(); const alreadySelected = options.every(({ value }) => selected.get(value)); const newSelected = new Map(selected); options.reduce((acc, current) => { if (!alreadySelected) { acc.set(current.value, true); } else { acc.delete(current.value); } return acc; }, newSelected); this.updateSelection(event, newSelected); } selectOne(event, id) { const selected = new Map(this.getSelectedMap()); if (selected.get(id)) { selected.delete(id); } else { selected.set(id, true); } this.updateSelection(event, selected); } updateSelection(event, selected) { if (this.props.onChange) { // controlled this.props.onChange(event, Array.from(selected.keys())); } else { // uncontrolled this.setState({ selected }); } } render() { const items = this.getListItems(); // If we don't have item to display, we show the message that tell there is no items const itemsDisplayed = Math.min(items.length, 6) || 1; const height = this.props.isLoading ? 120 : this.props.itemOptionRender.rowHeight * itemsDisplayed; const nbSelected = this.getSelectedMap().size; const viewHeight = this.props.itemViewRender.rowHeight * Math.min(6, nbSelected + 1); return /*#__PURE__*/_jsxs("div", { id: this.props.id, className: classnames('tc-multiselect', theme.container), ref: ref => { this.containerRef = ref; }, children: [/*#__PURE__*/_jsx(Icon, { name: "talend-caret-down", className: theme.caret, transform: this.state.showDropdown ? 'flip-vertical' : null }), !!nbSelected && /*#__PURE__*/_jsx(ActionButton, { icon: "talend-cross", bsStyle: "link", className: classnames('btn-icon-only', 'btn-sm', theme.clearall), label: this.props.t('MULTI_SELECT_LABEL_CLEAR_ALL', { defaultValue: 'Clear all' }), onClick: this.onClearAll, hideLabel: true }), /*#__PURE__*/_jsx("input", { type: "text" // eslint-disable-next-line , role: "search", className: "form-control", name: this.props.name, onBlur: this.props.onBlur, onChange: this.onSearchChange, onFocus: this.onInputFocus, onKeyDown: this.onInputKeyDown, disabled: this.props.disabled // eslint-disable-next-line , autoFocus: this.props.autoFocus, placeholder: this.props.placeholder, readOnly: this.props.readOnly, value: this.state.searchTerm, ref: ref => { this.inputRef = ref; } }), this.state.showDropdown && /*#__PURE__*/_jsx(Dropdown, { height: height, isLoading: this.props.isLoading, items: items, onRowClick: this.onRowClick, renderItem: this.props.itemOptionRender }), !this.state.showDropdown && nbSelected > 0 && /*#__PURE__*/_jsx("div", { style: { height: viewHeight }, children: /*#__PURE__*/_jsx(VirtualizedList, { type: "tc-multiselect", rowHeight: this.props.itemViewRender.rowHeight, rowRenderers: { 'tc-multiselect': this.props.itemViewRender }, collection: this.getSelectedItems(), onRowClick: this.onRowClick }) })] }); } } _defineProperty(MultiSelect, "displayName", 'MultiSelect'); _defineProperty(MultiSelect, "theme", theme); _defineProperty(MultiSelect, "defaultProps", { itemOptionRender: ItemOption, itemViewRender: ItemView, selected: [], options: [] }); _defineProperty(MultiSelect, "propTypes", { id: PropTypes.string.isRequired, name: PropTypes.string, placeholder: PropTypes.string, t: PropTypes.func, selected: PropTypes.arrayOf(PropTypes.string), itemOptionRender: PropTypes.func, itemViewRender: PropTypes.func, restricted: PropTypes.bool, readOnly: PropTypes.bool, disabled: PropTypes.bool, autoFocus: PropTypes.bool, isLoading: PropTypes.bool, onChange: PropTypes.func, onBlur: PropTypes.func, onFocus: PropTypes.func, options: PropTypes.array }); export default withTranslation(I18N_DOMAIN_COMPONENTS)(MultiSelect); //# sourceMappingURL=MultiSelect.container.js.map