// Simple Dropdown/Autocomplete with single/multiple selection and easy customisation via CSS modules // Version 2020-04-27 // License: LGPLv3.0+ // (c) Vitaliy Filippov 2019+ import React from 'react'; import PropTypes from 'prop-types'; import autocomplete_css from './autocomplete.css'; import PickerMenu from './PickerMenu.js'; export default class Selectbox extends React.PureComponent { static propTypes = { // multi-select multiple: PropTypes.bool, // make text input readonly (disable user input). still allows value change readOnly: PropTypes.bool, // show "clear" icon (cross) allowClear: PropTypes.bool, // select/autocomplete options - either an array of objects, or a { [string]: string } object source: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), // current value value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]), // change callback onChange: PropTypes.func, // item name key - default "name" labelKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // item id key - default "id" valueKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // automatically filter autocomplete options based on user input if `true` suggestionMatch: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['disabled'])]), // additional message to display below autocomplete options (arbitrary HTML, for example "...") suggestionMsg: PropTypes.any, // disable the whole input disabled: PropTypes.bool, // placeholder to display when the input is empty placeholder: PropTypes.string, // minimum suggestion list width in pixels minWidth: PropTypes.number, // change theme (CSS module) for this input theme: PropTypes.object, // additional CSS class name for the input className: PropTypes.string, // additional CSS styles for the input style: PropTypes.object, // additional event listener for onFocus onFocus: PropTypes.func, // additional event listener for onBlur onBlur: PropTypes.func, // additional event listener for user text input onQueryChange: PropTypes.func, } state = { shown: false, query: null, inputWidth: 20, } setQuery = (ev) => { const query = ev.target.value; this.setState({ query }); const f = this.props.onQueryChange; if (f) { f(query); } if (!query.length && !this.props.multiple && this.props.allowClear) { this.clear(); } } clear = () => { this.setState({ query: null }); const f = this.props.onChange; f && f(null); } removeValue = (ev) => { const n = ev.currentTarget.getAttribute('data-n'); if (n != null) { const v = [ ...this.props.value ]; v.splice(n, 1); const f = this.props.onChange; f && f(v); } } onSelectItem = (item) => { this.setState({ query: null }); this.picker.blur(); const sel = item[this.props.valueKey||'id']; let value = sel; if (this.props.multiple) { const already = (this.props.value||[]).indexOf(sel); if (already < 0) { // add value = [ ...(this.props.value||[]), sel ]; } else { // remove value = [ ...this.props.value ]; value.splice(already, 1); } } const f = this.props.onChange; f && f(value); } focusInput = () => { if (!this.props.disabled) { this.input.focus(); } } onFocus = () => { if (this.props.disabled) { return; } if (!this.props.multiple && this.picker.state.active === null) { const v = this.props.value, vk = this.props.valueKey||'id'; for (let i = 0; i < this.filtered_items.length; i++) { if (v == this.filtered_items[i][vk]) { this.picker.setState({ active: i }); break; } } } this.picker.focus(); const f = this.props.onFocus; f && f(); } onBlur = () => { this.picker.blur(); const f = this.props.onBlur; f && f(); if (!this.props.multiple && !this.props.allowClear && !this.input.value.length) { this.setState({ query: null }); } } renderInput = (p) => { const theme = this.props.theme || autocomplete_css; const value = this.state.query == null ? (this.props.multiple ? '' : this.item_hash[this.props.value]||'') : this.state.query; const input = ; return (