make expression input controlled (#6174)

* make expression input controlled

Signed-off-by: blalov <boyko.lalov@tick42.com>

* close menu explicitly  when autosuggestion dropdown is hidden

Signed-off-by: blalov <boyko.lalov@tick42.com>
This commit is contained in:
Boyko 2019-10-20 23:52:29 +03:00 committed by Julius Volz
parent 3acc3e856c
commit b5a16a8f86
2 changed files with 53 additions and 28 deletions

View File

@ -13,6 +13,7 @@ body {
.expression-input textarea { .expression-input textarea {
/* font-family: Menlo,Monaco,Consolas,'Courier New',monospace; */ /* font-family: Menlo,Monaco,Consolas,'Courier New',monospace; */
resize: none; resize: none;
overflow: hidden;
} }
button.execute-btn { button.execute-btn {

View File

@ -1,4 +1,3 @@
import $ from 'jquery';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { import {
Button, Button,
@ -8,7 +7,7 @@ import {
Input, Input,
} from 'reactstrap'; } from 'reactstrap';
import Downshift from 'downshift'; import Downshift, { ControllerStateAndHelpers } from 'downshift';
import fuzzy from 'fuzzy'; import fuzzy from 'fuzzy';
import SanitizeHTML from './components/SanitizeHTML'; import SanitizeHTML from './components/SanitizeHTML';
@ -25,10 +24,43 @@ interface ExpressionInputProps {
loading: boolean; loading: boolean;
} }
class ExpressionInput extends Component<ExpressionInputProps> { interface ExpressionInputState {
prevNoMatchValue: string | null = null; height: number | string;
value: string;
}
class ExpressionInput extends Component<ExpressionInputProps, ExpressionInputState> {
private prevNoMatchValue: string | null = null;
private exprInputRef = React.createRef<HTMLInputElement>(); private exprInputRef = React.createRef<HTMLInputElement>();
constructor(props: ExpressionInputProps) {
super(props);
this.state = {
value: props.value,
height: 'auto'
}
}
componentDidMount() {
this.setHeight();
}
setHeight = () => {
const { offsetHeight, clientHeight, scrollHeight } = this.exprInputRef.current!;
const offset = offsetHeight - clientHeight; // Needed in order for the height to be more accurate.
this.setState({ height: scrollHeight + offset });
}
handleInput = () => {
this.setState({
height: 'auto',
value: this.exprInputRef.current!.value
}, this.setHeight);
}
handleDropdownSelection = (value: string) => this.setState({ value });
handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => { handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && !event.shiftKey) { if (event.key === 'Enter' && !event.shiftKey) {
this.props.executeQuery(this.exprInputRef.current!.value); this.props.executeQuery(this.exprInputRef.current!.value);
@ -36,22 +68,23 @@ class ExpressionInput extends Component<ExpressionInputProps> {
} }
} }
renderAutosuggest = (downshift: any) => { executeQuery = () => this.props.executeQuery(this.exprInputRef.current!.value)
if (!downshift.isOpen) {
renderAutosuggest = (downshift: ControllerStateAndHelpers<any>) => {
const { inputValue } = downshift
if (!inputValue || (this.prevNoMatchValue && inputValue.includes(this.prevNoMatchValue))) {
downshift.closeMenu();
return null; return null;
} }
if (this.prevNoMatchValue && downshift.inputValue.includes(this.prevNoMatchValue)) { const matches = fuzzy.filter(inputValue.replace(/ /g, ''), this.props.metricNames, {
return null;
}
let matches = fuzzy.filter(downshift.inputValue.replace(/ /g, ''), this.props.metricNames, {
pre: "<strong>", pre: "<strong>",
post: "</strong>", post: "</strong>",
}); });
if (matches.length === 0) { if (matches.length === 0) {
this.prevNoMatchValue = downshift.inputValue; this.prevNoMatchValue = inputValue;
downshift.closeMenu();
return null; return null;
} }
@ -83,23 +116,12 @@ class ExpressionInput extends Component<ExpressionInputProps> {
); );
} }
componentDidMount() {
const $exprInput = $(this.exprInputRef.current!);
const resize = () => {
const el = $exprInput.get(0);
const offset = el.offsetHeight - el.clientHeight;
$exprInput.css('height', 'auto').css('height', el.scrollHeight + offset);
};
resize();
$exprInput.on('input', resize);
}
render() { render() {
const { value, height } = this.state;
return ( return (
<Downshift <Downshift
//inputValue={this.props.value} onChange={this.handleDropdownSelection}
//onInputValueChange={this.props.onChange} inputValue={value}
selectedItem={this.props.value}
> >
{(downshift) => ( {(downshift) => (
<div> <div>
@ -110,6 +132,8 @@ class ExpressionInput extends Component<ExpressionInputProps> {
</InputGroupText> </InputGroupText>
</InputGroupAddon> </InputGroupAddon>
<Input <Input
onInput={this.handleInput}
style={{ height }}
autoFocus autoFocus
type="textarea" type="textarea"
rows="1" rows="1"
@ -148,13 +172,13 @@ class ExpressionInput extends Component<ExpressionInputProps> {
<Button <Button
className="execute-btn" className="execute-btn"
color="primary" color="primary"
onClick={() => this.props.executeQuery(this.exprInputRef.current!.value)} onClick={this.executeQuery}
> >
Execute Execute
</Button> </Button>
</InputGroupAddon> </InputGroupAddon>
</InputGroup> </InputGroup>
{this.renderAutosuggest(downshift)} {downshift.isOpen && this.renderAutosuggest(downshift)}
</div> </div>
)} )}
</Downshift> </Downshift>