selectbox/Picker.js

152 lines
4.8 KiB
JavaScript
Raw Normal View History

2019-08-27 12:15:52 +03:00
// "Generic dropdown component"
// Renders something and then when that "something" is focused renders a popup layer next to it
// For example, a text input with a popup selection list
// ...Or maybe a button with a popup menu
2019-09-03 02:02:25 +03:00
// License: LGPLv3.0+
2019-08-27 12:15:52 +03:00
// (c) Vitaliy Filippov 2019+
2019-09-03 02:02:25 +03:00
// Version 2019-09-03
2019-08-27 12:15:52 +03:00
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
2019-09-03 02:02:25 +03:00
export default class Picker extends React.Component
2019-08-27 12:15:52 +03:00
{
static propTypes = {
direction: PropTypes.string,
clearOnClick: PropTypes.bool,
minWidth: PropTypes.number,
2019-08-30 02:38:35 +03:00
className: PropTypes.string,
2019-08-27 12:15:52 +03:00
style: PropTypes.object,
renderInput: PropTypes.func.isRequired,
renderPicker: PropTypes.func.isRequired,
}
state = {
focused: false,
height: 0,
width: 0,
top: 0,
left: 0,
}
focus = () =>
{
2019-08-30 18:20:43 +03:00
if (!this.state.focused)
2019-08-27 12:15:52 +03:00
{
2019-08-30 18:20:43 +03:00
this.setState({ focused: true, height: 0 });
this.calculateDirection();
if (this.props.clearOnClick)
{
document.body.addEventListener('click', this.blurExt);
}
2019-08-27 12:15:52 +03:00
}
}
blur = () =>
{
this.setState({ focused: false });
if (this.props.clearOnClick)
{
document.body.removeEventListener('click', this.blurExt);
}
}
blurExt = (ev) =>
{
let n = this.input ? ReactDOM.findDOMNode(this.input) : null;
let e = ev.target||ev.srcElement;
while (e)
{
// calendar-box is calendar.js's class
if (e == this.picker || e == n || /\bcalendar-box\b/.exec(e.className||''))
{
return;
}
e = e.parentNode;
}
this.blur();
}
setInput = (e) =>
{
this.input = e;
}
setPicker = (e) =>
{
this.picker = e;
}
render()
{
2019-09-03 02:02:25 +03:00
return (<React.Fragment>
2019-08-27 12:15:52 +03:00
{this.props.renderInput({
onFocus: this.focus,
onBlur: this.blur,
focused: this.state.focused,
ref: this.setInput,
})}
{this.state.focused
? <div style={{
position: 'fixed',
background: 'white',
2019-08-30 18:20:43 +03:00
height: this.state.height ? this.state.height+'px' : 'auto',
top: this.state.top+'px',
width: this.state.width ? this.state.width+'px' : 'auto',
left: this.state.left+'px',
2019-08-27 12:15:52 +03:00
zIndex: 100,
}} ref={this.setPicker}>
{this.props.renderPicker()}
</div>
: null}
2019-09-03 02:02:25 +03:00
</React.Fragment>);
2019-08-27 12:15:52 +03:00
}
componentDidUpdate()
{
if (this.state.focused && !this.state.height)
{
this.calculateDirection();
}
}
calculateDirection()
{
if (!this.input || !this.picker)
{
return;
}
2019-09-03 02:02:25 +03:00
const picker_size = ReactDOM.findDOMNode(this.picker).getBoundingClientRect();
2019-08-27 12:15:52 +03:00
const client = ReactDOM.findDOMNode(this.input).getBoundingClientRect();
2019-09-03 02:02:25 +03:00
const screen_width = window.innerWidth || document.documentElement.offsetWidth;
2019-08-27 12:15:52 +03:00
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
let direction = this.props.direction;
if (!direction || direction === 'auto')
{
2019-09-03 02:02:25 +03:00
const down = client.top + picker_size.height < screen_height;
2019-08-27 12:15:52 +03:00
direction = down ? 'down' : 'up';
}
let top = client.top
+ (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop)
- (document.documentElement.clientTop || document.body.clientTop || 0);
2019-08-30 18:20:43 +03:00
const max_height = (direction == 'down' ? screen_height-top-client.height-32 : top-32);
2019-09-03 02:02:25 +03:00
const height = picker_size.height < max_height ? picker_size.height : max_height;
2019-08-30 18:20:43 +03:00
top = direction == 'down' ? (top + client.height) : (top - height);
2019-09-03 02:02:25 +03:00
let left = (client.left
2019-08-27 12:15:52 +03:00
+ (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft)
2019-08-30 18:20:43 +03:00
- (document.documentElement.clientLeft || document.body.clientLeft || 0));
2019-09-03 02:02:25 +03:00
if (left + picker_size.width > screen_width)
{
left = screen_width - picker_size.width;
}
let width = client.width > picker_size.width ? client.width : picker_size.width;
width = (this.props.minWidth && width < this.props.minWidth ? this.props.minWidth : width);
2019-08-27 12:15:52 +03:00
if (this.state.top !== top || this.state.left !== left ||
2019-08-30 18:20:43 +03:00
this.state.width !== width || this.state.height !== height)
2019-08-27 12:15:52 +03:00
{
2019-08-30 18:20:43 +03:00
this.setState({ top, left, width, height });
2019-08-27 12:15:52 +03:00
}
}
}