mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			149 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
| import { debounce } from 'lodash';
 | |
| import React, { PureComponent } from 'react';
 | |
| 
 | |
| import { Field, Input } from '@grafana/ui';
 | |
| 
 | |
| interface Props {
 | |
|   value?: number;
 | |
|   placeholder?: string;
 | |
|   autoFocus?: boolean;
 | |
|   onChange: (number?: number) => void;
 | |
|   min?: number;
 | |
|   max?: number;
 | |
|   step?: number;
 | |
|   width?: number;
 | |
|   fieldDisabled?: boolean;
 | |
|   suffix?: React.ReactNode;
 | |
| }
 | |
| 
 | |
| interface State {
 | |
|   text: string;
 | |
|   inputCorrected: boolean;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This is an Input field that will call `onChange` for blur and enter
 | |
|  *
 | |
|  * @internal this is not exported to the `@grafana/ui` library, it is used
 | |
|  * by options editor (number and slider), and direclty with in grafana core
 | |
|  */
 | |
| 
 | |
| export class NumberInput extends PureComponent<Props, State> {
 | |
|   state: State = { text: '', inputCorrected: false };
 | |
|   inputRef = React.createRef<HTMLInputElement>();
 | |
| 
 | |
|   componentDidMount() {
 | |
|     this.setState({
 | |
|       text: isNaN(this.props.value!) ? '' : `${this.props.value}`,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   componentDidUpdate(oldProps: Props) {
 | |
|     if (this.props.value !== oldProps.value) {
 | |
|       const text = isNaN(this.props.value!) ? '' : `${this.props.value}`;
 | |
|       if (text !== this.state.text) {
 | |
|         this.setState({ text });
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   updateValue = () => {
 | |
|     const txt = this.inputRef.current?.value;
 | |
|     let corrected = false;
 | |
|     let newValue = '';
 | |
|     const min = this.props.min;
 | |
|     const max = this.props.max;
 | |
|     let currentValue = txt !== '' ? Number(txt) : undefined;
 | |
| 
 | |
|     if (currentValue && !Number.isNaN(currentValue)) {
 | |
|       if (min != null && currentValue < min) {
 | |
|         newValue = min.toString();
 | |
|         corrected = true;
 | |
|       } else if (max != null && currentValue > max) {
 | |
|         newValue = max.toString();
 | |
|         corrected = true;
 | |
|       } else {
 | |
|         newValue = txt ?? '';
 | |
|       }
 | |
| 
 | |
|       this.setState({
 | |
|         text: newValue,
 | |
|         inputCorrected: corrected,
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     if (corrected) {
 | |
|       this.updateValueDebounced();
 | |
|     }
 | |
| 
 | |
|     if (!Number.isNaN(currentValue) && currentValue !== this.props.value) {
 | |
|       this.props.onChange(currentValue);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   updateValueDebounced = debounce(this.updateValue, 500); // 1/2 second delay
 | |
| 
 | |
|   onChange = (e: React.FocusEvent<HTMLInputElement>) => {
 | |
|     this.setState({
 | |
|       text: e.currentTarget.value,
 | |
|     });
 | |
|     this.updateValueDebounced();
 | |
|   };
 | |
| 
 | |
|   onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
 | |
|     if (e.key === 'Enter') {
 | |
|       this.updateValue();
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   renderInput() {
 | |
|     return (
 | |
|       <Input
 | |
|         type="number"
 | |
|         ref={this.inputRef}
 | |
|         min={this.props.min}
 | |
|         max={this.props.max}
 | |
|         step={this.props.step}
 | |
|         autoFocus={this.props.autoFocus}
 | |
|         value={this.state.text}
 | |
|         onChange={this.onChange}
 | |
|         onBlur={this.updateValue}
 | |
|         onKeyPress={this.onKeyPress}
 | |
|         placeholder={this.props.placeholder}
 | |
|         disabled={this.props.fieldDisabled}
 | |
|         width={this.props.width}
 | |
|         suffix={this.props.suffix}
 | |
|       />
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   render() {
 | |
|     const { inputCorrected } = this.state;
 | |
|     if (inputCorrected) {
 | |
|       let range = '';
 | |
|       let { min, max } = this.props;
 | |
|       if (max == null) {
 | |
|         if (min != null) {
 | |
|           range = `< ${min}`;
 | |
|         }
 | |
|       } else if (min != null) {
 | |
|         range = `${min} < > ${max}`;
 | |
|       } else {
 | |
|         range = `> ${max}`;
 | |
|       }
 | |
|       return (
 | |
|         <Field
 | |
|           invalid={inputCorrected}
 | |
|           error={`Out of range ${range}`}
 | |
|           validationMessageHorizontalOverflow={true}
 | |
|           style={{ direction: 'rtl' }}
 | |
|         >
 | |
|           {this.renderInput()}
 | |
|         </Field>
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return this.renderInput();
 | |
|   }
 | |
| }
 |