Farmbot-Web-App/frontend/ui/blurable_input.tsx

132 lines
3.8 KiB
TypeScript
Raw Permalink Normal View History

2017-06-29 12:54:02 -06:00
import * as React from "react";
2019-01-13 16:39:26 -07:00
import { equals, parseIntInput } from "../util";
2018-07-10 21:05:06 -06:00
import { isNumber } from "lodash";
2019-01-13 16:39:26 -07:00
import { InputError } from "./input_error";
2019-04-02 13:59:37 -06:00
import { t } from "../i18next_wrapper";
2019-06-24 15:39:49 -06:00
import { error } from "../toast/toast";
2017-06-29 12:54:02 -06:00
export interface BIProps {
value: string | number;
2017-06-29 12:54:02 -06:00
onCommit(e: React.SyntheticEvent<HTMLInputElement>): void;
min?: number;
max?: number;
type?:
| "text"
| "number"
| "email"
| "time"
| "date"
| "hidden";
2017-06-29 12:54:02 -06:00
name?: string;
id?: string;
/** Allow the user to empty out the form control. If unset, form control
2018-06-14 11:22:00 -06:00
* will reset itself to previous defaultValue. */
2017-06-29 12:54:02 -06:00
allowEmpty?: boolean;
disabled?: boolean;
className?: string;
placeholder?: string;
hidden?: boolean;
2019-01-13 16:39:26 -07:00
error?: string;
title?: string;
2019-02-10 22:10:29 -07:00
autoFocus?: boolean;
2019-12-27 11:38:29 -07:00
autoSelect?: boolean;
2017-06-29 12:54:02 -06:00
}
interface BIState {
buffer: string;
isEditing: boolean;
2019-01-13 16:39:26 -07:00
error: string | undefined;
2017-06-29 12:54:02 -06:00
}
export class BlurableInput extends React.Component<BIProps, Partial<BIState>> {
2019-01-13 16:39:26 -07:00
state: BIState = { buffer: "", isEditing: false, error: undefined };
get error() { return this.props.error || this.state.error; }
withinLimits = (options?: { toasts?: boolean }): boolean => {
const onError = (msg: string) => {
this.setState({ error: msg });
2020-01-03 13:04:45 -07:00
options?.toasts && error(msg);
2019-01-13 16:39:26 -07:00
};
2017-06-29 12:54:02 -06:00
2018-07-10 21:05:06 -06:00
if (this.props.type === "number") {
2019-01-13 16:39:26 -07:00
const value = parseIntInput(this.state.buffer);
2018-07-10 21:05:06 -06:00
if (isNumber(this.props.min) && value < this.props.min) {
2019-01-13 16:39:26 -07:00
onError(t("Value must be greater than or equal to {{min}}.",
2018-07-10 21:05:06 -06:00
{ min: this.props.min }));
return false;
}
if (isNumber(this.props.max) && value > this.props.max) {
2019-01-13 16:39:26 -07:00
onError(t("Value must be less than or equal to {{max}}.",
2018-07-10 21:05:06 -06:00
{ max: this.props.max }));
return false;
}
2019-01-13 16:39:26 -07:00
/** `e.currentTarget.value` is "" for any invalid number input. */
if ((this.state.buffer === "") && !this.props.allowEmpty) {
onError(t("Please enter a number."));
return false;
}
2018-07-10 21:05:06 -06:00
}
2019-01-13 16:39:26 -07:00
this.setState({ error: undefined });
2018-07-10 21:05:06 -06:00
return true;
}
2017-06-29 12:54:02 -06:00
/** Called on blur. */
maybeCommit = (e: React.SyntheticEvent<HTMLInputElement>) => {
2018-07-10 21:05:06 -06:00
const bufferOk = this.state.buffer || this.props.allowEmpty;
2019-01-13 16:39:26 -07:00
const shouldPassToParent = bufferOk && this.withinLimits({ toasts: true });
2018-06-15 07:37:13 -06:00
shouldPassToParent && this.props.onCommit(e);
2019-01-13 16:39:26 -07:00
this.setState({ isEditing: false, buffer: "", error: undefined });
2017-06-29 12:54:02 -06:00
}
2019-12-27 11:38:29 -07:00
focus = (e: React.FocusEvent<HTMLInputElement>) => {
2017-08-28 05:44:37 -06:00
const { value } = this.props;
2019-12-27 11:38:29 -07:00
this.props.autoSelect &&
e.target.setSelectionRange(0, e.target.value.length);
2019-01-13 16:39:26 -07:00
this.setState({
isEditing: true,
buffer: "" + (value || ""),
error: undefined
});
2017-06-29 12:54:02 -06:00
}
updateBuffer = (e: React.SyntheticEvent<HTMLInputElement>) => {
2019-01-13 16:39:26 -07:00
this.setState({ buffer: e.currentTarget.value }, this.withinLimits);
2017-06-29 12:54:02 -06:00
}
usualProps = () => {
2018-06-14 11:27:00 -06:00
const value = this.state.isEditing ?
this.state.buffer : this.props.value;
2017-08-28 05:44:37 -06:00
return {
value,
2017-08-28 05:44:37 -06:00
hidden: !!this.props.hidden,
onFocus: this.focus,
onChange: this.updateBuffer,
onSubmit: this.maybeCommit,
onBlur: this.maybeCommit,
name: this.props.name,
id: this.props.id,
2018-07-10 21:05:06 -06:00
min: this.props.min,
max: this.props.max,
2017-08-28 05:44:37 -06:00
type: this.props.type || "text",
disabled: this.props.disabled,
2019-01-13 16:39:26 -07:00
className: (this.props.className || "") + (this.error ? " error" : ""),
title: this.props.title || "",
2017-08-28 05:44:37 -06:00
placeholder: this.props.placeholder,
2019-02-10 22:10:29 -07:00
autoFocus: this.props.autoFocus,
2017-08-28 05:44:37 -06:00
};
}
2017-12-13 14:46:42 -07:00
shouldComponentUpdate(nextProps: BIProps, nextState: Partial<BIState>) {
return !equals(this.props, nextProps) || !equals(this.state, nextState);
}
2017-12-13 14:23:28 -07:00
2017-06-29 12:54:02 -06:00
render() {
2019-01-14 21:49:09 -07:00
return <div className="input">
2019-01-13 16:39:26 -07:00
<InputError error={this.error} />
<input {...this.usualProps()} />
</div>;
2017-06-29 12:54:02 -06:00
}
}