@talend/react-components
Version:
Set of react components.
329 lines (325 loc) • 10.4 kB
JavaScript
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