// Menu-like Picker variant with keyboard control // Version 2020-06-30 // License: LGPLv3.0+ // (c) Vitaliy Filippov 2020+ import React from 'react'; import PropTypes from 'prop-types'; import autocomplete_css from './autocomplete.css'; import Picker from './Picker.js'; export default class PickerMenu extends Picker { static propTypes = { ...Picker.propTypes, // menu options items: PropTypes.array, // additional text/items to render before menu items beforeItems: PropTypes.any, // additional text/items to render after menu items afterItems: PropTypes.any, // menuitem callback onSelectItem: PropTypes.func, // don't hide the menu on item click keepOnClick: PropTypes.bool, // menuitem name key - default empty (render the item itself) labelKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // change theme (CSS module) for this input theme: PropTypes.object, } onKeyDown = (ev) => { if ((ev.which == 40 || ev.which == 38) && this.props.items.length) { // up / down this.setState({ active: this.state.active == null ? 0 : ( (this.state.active + (event.which === 40 ? 1 : this.props.items.length-1)) % this.props.items.length ), }); if (!this.state.focused) { this.focus(); } } else if (ev.which == 10 || ev.which == 13) { // enter if (!this.state.focused) { this.focus(); } else if (this.state.active != null && this.state.active < this.props.items.length) { this.onMouseDown(); } } } onMouseDown = (ev) => { if (!this.props.keepOnClick) { if (this.input) { this.input.blur(); } this.blur(); } else if (ev) { ev.preventDefault(); } const sel = this.props.items[this.state.active]; const f = this.props.onSelectItem; if (f) { f(sel); } else if (sel.onClick) { sel.onClick(); } } onMouseOver = (ev) => { let e = ev.target; while (e && e != ev.currentTarget && !e.id) { e = e.parentNode; } if (e && e.id) { this.setState({ active: e.id }); } } animatePicker = (e) => { if (e) { if (!this.props.renderInput) { e.focus(); } e.style.visibility = 'hidden'; e.style.overflowY = 'hidden'; const anim = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame; anim(() => { e.style.visibility = ''; e.style.maxHeight = '1px'; anim(() => { e.style.maxHeight = '100%'; const end = () => { e.style.overflowY = 'auto'; e.removeEventListener('transitionend', end); }; e.addEventListener('transitionend', end); }); }); } } renderPicker = () => { const theme = this.props.theme || autocomplete_css; return (
{this.props.beforeItems} {this.props.items.map((e, i) => (
{!this.props.labelKey ? e : e[this.props.labelKey]}
))} {this.props.afterItems}
); } getInputProps() { return { ...super.getInputProps(), onKeyDown: this.onKeyDown, }; } componentDidUpdate() { super.componentDidUpdate(); if (this.input && this.state.focused) { if (this.prevHeight && this.input.offsetHeight != this.prevHeight) { this.calculateDirection(); } this.prevHeight = this.input.offsetHeight; } } } delete PickerMenu.propTypes.renderPicker;