2020-04-27 15:59:26 +03:00
|
|
|
// Menu-like Picker variant with keyboard control
|
2020-06-23 15:29:59 +03:00
|
|
|
// Version 2020-06-22
|
2020-04-27 15:59:26 +03:00
|
|
|
// 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,
|
2020-04-27 18:21:12 +03:00
|
|
|
// don't hide the menu on item click
|
|
|
|
keepOnClick: PropTypes.bool,
|
2020-04-27 15:59:26 +03:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|
2020-04-27 19:03:07 +03:00
|
|
|
else if (ev.which == 10 || ev.which == 13)
|
2020-04-27 15:59:26 +03:00
|
|
|
{
|
|
|
|
// enter
|
2020-04-27 19:03:07 +03:00
|
|
|
if (!this.state.focused)
|
|
|
|
{
|
|
|
|
this.focus();
|
|
|
|
}
|
|
|
|
else if (this.state.active != null && this.state.active < this.props.items.length)
|
|
|
|
{
|
|
|
|
this.onMouseDown();
|
|
|
|
}
|
2020-04-27 15:59:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 18:21:12 +03:00
|
|
|
onMouseDown = (ev) =>
|
2020-04-27 15:59:26 +03:00
|
|
|
{
|
2020-04-27 19:00:05 +03:00
|
|
|
if (!this.props.keepOnClick)
|
2020-04-27 18:21:12 +03:00
|
|
|
{
|
2020-04-27 19:00:05 +03:00
|
|
|
this.blur();
|
|
|
|
}
|
|
|
|
else if (ev)
|
|
|
|
{
|
|
|
|
ev.preventDefault();
|
2020-04-27 18:21:12 +03:00
|
|
|
}
|
2020-04-27 15:59:26 +03:00
|
|
|
const sel = this.props.items[this.state.active];
|
|
|
|
const f = this.props.onSelectItem;
|
2020-04-27 21:53:59 +03:00
|
|
|
if (f)
|
|
|
|
{
|
|
|
|
f(sel);
|
|
|
|
}
|
|
|
|
else if (sel.onClick)
|
|
|
|
{
|
|
|
|
sel.onClick();
|
|
|
|
}
|
2020-04-27 15:59:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2020-06-23 15:29:59 +03:00
|
|
|
if (!this.props.renderInput)
|
2020-04-27 18:37:26 +03:00
|
|
|
{
|
|
|
|
e.focus();
|
|
|
|
}
|
2020-04-27 15:59:26 +03:00
|
|
|
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 (<div ref={this.animatePicker}
|
2020-04-27 18:37:26 +03:00
|
|
|
tabIndex={!this.props.renderInput ? 1 : undefined}
|
2020-04-27 15:59:26 +03:00
|
|
|
className={theme.suggestions}
|
2020-04-27 18:37:26 +03:00
|
|
|
onKeyDown={this.onKeyDown}
|
2020-04-27 15:59:26 +03:00
|
|
|
onMouseOver={this.onMouseOver}>
|
|
|
|
{this.props.beforeItems}
|
|
|
|
{this.props.items.map((e, i) => (<div key={i} id={i} onMouseDown={this.onMouseDown}
|
|
|
|
className={theme.suggestion+(this.state.active == i ? ' '+theme.active : '')}>
|
|
|
|
{!this.props.labelKey ? e : e[this.props.labelKey]}
|
|
|
|
</div>))}
|
|
|
|
{this.props.afterItems}
|
|
|
|
</div>);
|
|
|
|
}
|
|
|
|
|
|
|
|
getInputProps()
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
...super.getInputProps(),
|
|
|
|
onKeyDown: this.onKeyDown,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate()
|
|
|
|
{
|
|
|
|
super.componentDidUpdate();
|
2020-04-27 18:21:12 +03:00
|
|
|
if (this.input && this.state.focused)
|
2020-04-27 15:59:26 +03:00
|
|
|
{
|
|
|
|
if (this.prevHeight && this.input.offsetHeight != this.prevHeight)
|
|
|
|
{
|
|
|
|
this.calculateDirection();
|
|
|
|
}
|
|
|
|
this.prevHeight = this.input.offsetHeight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete PickerMenu.propTypes.renderPicker;
|