diff --git a/Picker.js b/Picker.js index 77453b9..2b3cb50 100644 --- a/Picker.js +++ b/Picker.js @@ -14,12 +14,14 @@ export default class Picker extends React.Component { static propTypes = { direction: PropTypes.string, - clearOnClick: PropTypes.bool, + autoHide: PropTypes.bool, minWidth: PropTypes.number, className: PropTypes.string, - style: PropTypes.object, - renderInput: PropTypes.func.isRequired, + renderInput: PropTypes.func, renderPicker: PropTypes.func.isRequired, + popupX: PropTypes.number, + popupY: PropTypes.number, + onHide: PropTypes.func, } state = { @@ -32,23 +34,22 @@ export default class Picker extends React.Component focus = () => { - if (!this.state.focused) + if (!this.state.focused && this.props.renderInput) { this.setState({ focused: true, height: 0 }); - this.calculateDirection(); - if (this.props.clearOnClick) - { - document.body.addEventListener('click', this.blurExt); - } } } blur = () => { - this.setState({ focused: false }); - if (this.props.clearOnClick) + if (this.state.focused || !this.props.renderInput) { - document.body.removeEventListener('click', this.blurExt); + if (this.props.renderInput) + { + this.setState({ focused: false }); + } + const f = this.props.onHide; + f && f(); } } @@ -96,8 +97,8 @@ export default class Picker extends React.Component render() { return ( - {this.props.renderInput(this.getInputProps())} - {this.state.focused + {this.props.renderInput && this.props.renderInput(this.getInputProps())} + {!this.props.renderInput || this.state.focused ?
); } + componentDidMount() + { + this.componentDidUpdate(); + } + componentDidUpdate() { - if (this.state.focused && !this.state.height) + if (!this.props.renderInput || this.state.focused) { - this.calculateDirection(); + if (!this.state.height) + { + this.calculateDirection(); + } + if (this.props.autoHide && !this._blurSet) + { + this._blurSet = true; + document.body.addEventListener('click', this.blurExt); + } + } + else if (this._blurSet) + { + this._blurSet = false; + document.body.removeEventListener('click', this.blurExt); + } + } + + componentWillUnmount() + { + if (this._blurSet) + { + this._blurSet = false; + document.body.removeEventListener('click', this.blurExt); } } calculateDirection() { - if (!this.input || !this.picker) + if (!this.picker) { return; } - const picker_size = ReactDOM.findDOMNode(this.picker).getBoundingClientRect(); - const client = ReactDOM.findDOMNode(this.input).getBoundingClientRect(); - const screen_width = window.innerWidth || document.documentElement.offsetWidth; - const screen_height = window.innerHeight || document.documentElement.offsetHeight; - let direction = this.props.direction; - if (!direction || direction === 'auto') + const inputRect = this.input + ? ReactDOM.findDOMNode(this.input).getBoundingClientRect() + : { left: this.props.popupX||0, top: this.props.popupY||0 }; + const pos = Picker.calculatePopupPosition(inputRect, this.picker, this.props); + if (this.state.top !== pos.top || this.state.left !== pos.left || + this.state.width !== pos.width || this.state.height !== pos.height) { - const down = client.top + picker_size.height < screen_height; - direction = down ? 'down' : 'up'; - } - let top = client.top - + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) - - (document.documentElement.clientTop || document.body.clientTop || 0); - const max_height = (direction == 'down' ? screen_height-top-client.height-32 : top-32); - const height = picker_size.height < max_height ? picker_size.height : max_height; - top = direction == 'down' ? (top + client.height) : (top - height); - let left = (client.left - + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) - - (document.documentElement.clientLeft || document.body.clientLeft || 0)); - 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); - if (this.state.top !== top || this.state.left !== left || - this.state.width !== width || this.state.height !== height) - { - this.setState({ top, left, width, height }); + this.setState(pos); } } + + static calculatePopupPosition(clientRect, popup, props) + { + const popup_size = ReactDOM.findDOMNode(popup).getBoundingClientRect(); + const screen_width = window.innerWidth || document.documentElement.offsetWidth; + const screen_height = window.innerHeight || document.documentElement.offsetHeight; + let direction = props && props.direction; + if (!direction || direction === 'auto') + { + const down = clientRect.top + popup_size.height < screen_height || + clientRect.top < screen_height/2; + direction = down ? 'down' : 'up'; + } + let top = clientRect.top + + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) + - (document.documentElement.clientTop || document.body.clientTop || 0); + const max_height = (direction == 'down' ? screen_height-top-(clientRect.height||0)-32 : top-32); + const height = Math.round(popup_size.height < max_height ? popup_size.height : max_height); + top = direction == 'down' ? (top + (clientRect.height||0)) : (top - height); + let left = (clientRect.left + + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) + - (document.documentElement.clientLeft || document.body.clientLeft || 0)); + if (left + popup_size.width > screen_width) + { + left = screen_width - popup_size.width; + } + let width = (clientRect.width||0) > popup_size.width ? clientRect.width : popup_size.width; + width = Math.round(props && props.minWidth && width < props.minWidth ? props.minWidth : width); + return { top, left, width, height }; + } } diff --git a/PickerMenu.js b/PickerMenu.js index 28013bc..0c0d77e 100644 --- a/PickerMenu.js +++ b/PickerMenu.js @@ -21,6 +21,8 @@ export default class PickerMenu extends Picker 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 @@ -50,8 +52,12 @@ export default class PickerMenu extends Picker } } - onMouseDown = () => + onMouseDown = (ev) => { + if (ev) + { + this.props.keepOnClick ? ev.preventDefault() : this.blur(); + } const sel = this.props.items[this.state.active]; const f = this.props.onSelectItem; f && f(sel); @@ -117,7 +123,7 @@ export default class PickerMenu extends Picker componentDidUpdate() { super.componentDidUpdate(); - if (this.input) + if (this.input && this.state.focused) { if (this.prevHeight && this.input.offsetHeight != this.prevHeight) { @@ -126,11 +132,6 @@ export default class PickerMenu extends Picker this.prevHeight = this.input.offsetHeight; } } - - componentDidMount() - { - this.componentDidUpdate(); - } } delete PickerMenu.propTypes.renderPicker; diff --git a/Selectbox.js b/Selectbox.js index 8b34e6e..a4dd1b8 100644 --- a/Selectbox.js +++ b/Selectbox.js @@ -24,6 +24,8 @@ export default class Selectbox extends React.PureComponent value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]), // change callback onChange: PropTypes.func, + // do not hide suggestion list on change + keepFocusOnChange: PropTypes.bool, // item name key - default "name" labelKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // item id key - default "id" @@ -95,7 +97,11 @@ export default class Selectbox extends React.PureComponent onSelectItem = (item) => { this.setState({ query: null }); - this.picker.blur(); + if (this.props.keepFocusOnChange === false || + this.props.keepFocusOnChange == null && !this.props.multiple) + { + this.picker.blur(); + } const sel = item[this.props.valueKey||'id']; let value = sel; if (this.props.multiple) @@ -265,7 +271,9 @@ export default class Selectbox extends React.PureComponent return ( + { + this.setState({ ctx_x: ev.pageX, ctx_y: ev.pageY }); + ev.preventDefault(); + } + + hideContextMenu = (ev) => + { + this.setState({ ctx_x: null, ctx_y: null }); + } + render() { return
@@ -38,6 +49,7 @@ class Test extends React.PureComponent source={OPTIONS} allowClear={true} multiple={true} + keepFocusOnChange={true} placeholder="Выберите значение" suggestionMatch={true} value={this.state.value} @@ -74,10 +86,31 @@ class Test extends React.PureComponent onChange={this.onChange2} /> } + autoHide={true} + minWidth={200} + renderInput={p => } items={NAMES} /> +
+ Кликните сюда правой кнопкой для вызова контекстного меню +
+ {this.state.ctx_x != null + ? + : null}
; } }