Allow to use Portal for rendering Picker, rework animation
parent
c3cc77f244
commit
f1f7941bf2
87
Picker.js
87
Picker.js
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
/>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue