Render autocomplete suggestions in a Portal

dev
Vitaliy Filippov 2018-07-24 03:04:05 +03:00
parent 83dd1c93b7
commit 80bbb584db
2 changed files with 33 additions and 36 deletions

View File

@ -9,6 +9,7 @@ import { AUTOCOMPLETE } from '../identifiers.js';
import InjectChip from '../chip/Chip.js'; import InjectChip from '../chip/Chip.js';
import InjectInput from '../input/Input.js'; import InjectInput from '../input/Input.js';
import events from '../utils/events.js'; import events from '../utils/events.js';
import Portal from '../hoc/Portal';
const POSITION = { const POSITION = {
AUTO: 'auto', AUTO: 'auto',
@ -77,7 +78,6 @@ const factory = (Chip, Input) => {
}; };
state = { state = {
direction: this.props.direction,
focus: false, focus: false,
showAllSuggestions: this.props.showSuggestionsWhenValueIsSet, showAllSuggestions: this.props.showSuggestionsWhenValueIsSet,
query: this.props.query ? this.props.query : this.query(this.props.value), query: this.props.query ? this.props.query : this.query(this.props.value),
@ -92,11 +92,8 @@ const factory = (Chip, Input) => {
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if (!this.state.focus && nextState.focus && this.props.direction === POSITION.AUTO) { if (!this.state.focus && nextState.focus) {
const direction = this.calculateDirection(); this.calculateDirection();
if (this.state.direction !== direction) {
this.setState({ direction });
}
} }
return true; return true;
} }
@ -178,13 +175,26 @@ const factory = (Chip, Input) => {
}; };
calculateDirection() { calculateDirection() {
const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect();
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
let direction = this.props.direction;
if (this.props.direction === 'auto') { if (this.props.direction === 'auto') {
const client = ReactDOM.findDOMNode(this.inputNode).getBoundingClientRect();
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
const up = client.top > ((screen_height / 2) + client.height); const up = client.top > ((screen_height / 2) + client.height);
return up ? 'up' : 'down'; direction = up ? 'up' : 'down';
}
const top = direction == 'down' ? (client.top+client.height)+'px' : client.top+'px';
const bottom = direction == 'up' ? '0px' : undefined;
const left = client.left+'px';
const width = client.width+'px';
let maxHeight = direction == 'down' ? (screen_height-client.top-client.height) : client.top;
if (maxHeight > screen_height*0.45) {
maxHeight = Math.floor(screen_height*0.45);
}
if (this.state.top !== top || this.state.left !== left ||
this.state.width !== width || this.state.bottom !== bottom ||
this.state.maxHeight !== maxHeight) {
this.setState({ top, bottom, left, width, maxHeight });
} }
return this.props.direction;
} }
query(key) { query(key) {
@ -383,13 +393,13 @@ const factory = (Chip, Input) => {
); );
}); });
return ( const { top, bottom, maxHeight, left, width } = this.state;
<ul return (<div style={{position: 'absolute', top, left, width}}>
className={classnames(theme.suggestions, { [theme.up]: this.state.direction === 'up' })} <ul style={{bottom, maxHeight}}
> className={theme.suggestions}>
{suggestions} {suggestions}
</ul> </ul>
); </div>);
} }
render() { render() {
@ -425,7 +435,9 @@ const factory = (Chip, Input) => {
themeNamespace="input" themeNamespace="input"
value={this.state.query} value={this.state.query}
/> />
{this.renderSuggestions()} <Portal>
{this.state.focus ? this.renderSuggestions() : null}
</Portal>
{this.props.selectedPosition === 'below' ? this.renderSelected() : null} {this.props.selectedPosition === 'below' ? this.renderSelected() : null}
</div> </div>
); );

View File

@ -8,15 +8,6 @@
position: relative; position: relative;
@apply --reset; @apply --reset;
&.focus {
& .suggestions {
box-shadow: var(--zdepth-shadow-1);
max-height: var(--autocomplete-overflow-max-height);
-ms-overflow-style: none;
visibility: visible;
}
}
} }
.clear { .clear {
@ -46,27 +37,21 @@
} }
.suggestions { .suggestions {
position: absolute;
background-color: var(--autocomplete-suggestions-background); background-color: var(--autocomplete-suggestions-background);
list-style: none; list-style: none;
max-height: 0; max-height: 0;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
padding: 0; padding: 0;
position: absolute;
transition-duration: var(--animation-duration); transition-duration: var(--animation-duration);
transition-property: max-height, box-shadow; transition-property: max-height, box-shadow;
transition-timing-function: var(--animation-curve-default); transition-timing-function: var(--animation-curve-default);
visibility: hidden;
width: 100%; width: 100%;
z-index: var(--z-index-high); z-index: var(--z-index-highest);
box-shadow: var(--zdepth-shadow-1);
&:not(.up) { max-height: var(--autocomplete-overflow-max-height);
margin-top: calc(-1 * var(--input-padding)); -ms-overflow-style: none;
}
&.up {
bottom: 0;
}
&::-webkit-scrollbar { &::-webkit-scrollbar {
height: 0; height: 0;