Allow to use Portal for rendering Picker, rework animation

master
Vitaliy Filippov 2021-09-16 01:14:56 +03:00
parent c3cc77f244
commit f1f7941bf2
4 changed files with 72 additions and 55 deletions

View File

@ -4,7 +4,7 @@
// ...Or maybe a button with a popup menu
// License: LGPLv3.0+
// (c) Vitaliy Filippov 2019+
// Version 2021-09-09
// Version 2021-09-14
import React from 'react';
import ReactDOM from 'react-dom';
@ -22,6 +22,7 @@ export default class Picker extends React.Component
popupX: PropTypes.number,
popupY: PropTypes.number,
onHide: PropTypes.func,
usePortal: PropTypes.bool,
}
state = {
@ -98,21 +99,67 @@ export default class Picker extends React.Component
});
}
animatePicker = (e) =>
{
if (e)
{
e = ReactDOM.findDOMNode(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.transitionTimingFunction = 'cubic-bezier(0.4, 0, 0.2, 1)';
e.style.transitionDuration = '0.2s';
e.style.transitionProperty = 'max-height';
e.style.maxHeight = '100%';
const end = () =>
{
e.style.transitionProperty = '';
e.style.maxHeight = '';
e.style.overflowY = '';
e.removeEventListener('transitionend', end);
};
e.addEventListener('transitionend', end);
});
});
}
}
render()
{
return (<React.Fragment>
{this.props.renderInput && this.props.renderInput(this.getInputProps())}
{!this.props.renderInput || this.state.focused
? <div style={{
position: 'fixed',
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',
zIndex: 100,
}} ref={this.setPicker}>
{this.renderPicker()}
</div>
? (this.props.usePortal
? ReactDOM.createPortal(<div style={{
position: 'absolute',
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',
zIndex: 100,
}} ref={this.setPicker}>
{this.renderPicker()}
</div>, document.body)
: <div style={{
position: 'fixed',
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',
zIndex: 100,
}} ref={this.setPicker}>
{this.renderPicker()}
</div>)
: null}
</React.Fragment>);
}
@ -169,7 +216,7 @@ export default class Picker extends React.Component
}
}
static calculatePopupPosition(clientRect, popup, props)
static calculatePopupPosition(input_pos, popup, props)
{
const popup_size = ReactDOM.findDOMNode(popup).getBoundingClientRect();
const screen_width = window.innerWidth || document.documentElement.offsetWidth;
@ -177,22 +224,24 @@ export default class Picker extends React.Component
let direction = props && props.direction;
if (!direction || direction === 'auto')
{
const down = clientRect.top + popup_size.height < screen_height ||
clientRect.top < screen_height/2;
const down = input_pos.top + popup_size.height < screen_height ||
input_pos.top < screen_height/2;
direction = down ? 'down' : 'up';
}
let top = clientRect.top
let top = input_pos.top
+ (props.usePortal ? (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) : 0)
- (document.documentElement.clientTop || document.body.clientTop || 0);
const max_height = (direction == 'down' ? screen_height-top-(clientRect.height||0)-32 : top-32);
const max_height = (direction == 'down' ? screen_height-top-(input_pos.height||0)-32 : top-32);
const height = Math.ceil(popup_size.height < max_height ? popup_size.height : max_height);
top = direction == 'down' ? (top + (clientRect.height||0)) : (top - height);
let left = (clientRect.left
top = direction == 'down' ? (top + (input_pos.height||0)) : (top - height);
let left = (input_pos.left
+ (props.usePortal ? (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) : 0)
- (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;
let width = (input_pos.width||0) > popup_size.width ? input_pos.width : popup_size.width;
width = Math.ceil(props && props.minWidth && width < props.minWidth ? props.minWidth : width);
return { top, left, width, height };
}

View File

@ -117,36 +117,6 @@ export default class PickerMenu extends Picker
}
}
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.maxHeight = 'none';
e.style.overflowY = 'auto';
e.removeEventListener('transitionend', end);
};
e.addEventListener('transitionend', end);
});
});
}
}
renderPicker = () =>
{
const theme = this.props.theme || autocomplete_css;

View File

@ -1,5 +1,5 @@
// Simple Dropdown/Autocomplete with single/multiple selection and easy customisation via CSS modules
// Version 2021-08-30
// Version 2021-09-14
// License: LGPLv3.0+
// (c) Vitaliy Filippov 2019+
@ -289,6 +289,7 @@ export default class Selectbox extends React.PureComponent
disabledKey={this.props.disabledKey}
afterItems={this.props.suggestionMsg}
onSelectItem={this.onSelectItem}
usePortal={this.props.usePortal}
/>);
}

View File

@ -43,7 +43,6 @@
overflow: hidden;
cursor: text;
height: auto;
padding: 1px 0;
}
.input.readonly, .input.readonly .inputElement
@ -60,15 +59,12 @@
overflow-x: hidden;
overflow-y: auto;
padding: 0;
transition-duration: 0.2s;
transition-property: max-height;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
width: 100%;
max-height: 100%;
}
.suggestion
{
font-family: helvetica, arial, verdana, sans-serif;
font-size: 13px;
padding: 7px;
user-select: none;
@ -90,6 +86,7 @@
{
background: transparent;
font-size: inherit;
font-weight: inherit;
line-height: 200%;
border: 0;
padding: 0 2em 0 .5em;