Convert EditableText to TS

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-09-16 19:33:17 +02:00
parent af853e1d86
commit 03ce568a5d
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D

View file

@ -16,33 +16,42 @@ limitations under the License.
*/ */
import React, { createRef } from 'react'; import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import { Key } from "../../../Keyboard"; import { Key } from "../../../Keyboard";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
enum Phases {
Display = "display",
Edit = "edit",
}
interface IProps {
onValueChanged?: (value: string, shouldSubmit: boolean) => void;
initialValue?: string;
label?: string;
placeholder?: string;
className?: string;
labelClassName?: string;
placeholderClassName?: string;
// Overrides blurToSubmit if true
blurToCancel?: boolean;
// Will cause onValueChanged(value, true) to fire on blur
blurToSubmit?: boolean;
editable?: boolean;
}
interface IState {
phase: Phases;
}
@replaceableComponent("views.elements.EditableText") @replaceableComponent("views.elements.EditableText")
export default class EditableText extends React.Component { export default class EditableText extends React.Component<IProps, IState> {
static propTypes = { // we track value as an JS object field rather than in React state
onValueChanged: PropTypes.func, // as React doesn't play nice with contentEditable.
initialValue: PropTypes.string, public value = '';
label: PropTypes.string, private placeholder = false;
placeholder: PropTypes.string, private editableDiv = createRef<HTMLDivElement>();
className: PropTypes.string,
labelClassName: PropTypes.string,
placeholderClassName: PropTypes.string,
// Overrides blurToSubmit if true
blurToCancel: PropTypes.bool,
// Will cause onValueChanged(value, true) to fire on blur
blurToSubmit: PropTypes.bool,
editable: PropTypes.bool,
};
static Phases = { public static defaultProps: Partial<IProps> = {
Display: "display",
Edit: "edit",
};
static defaultProps = {
onValueChanged() {}, onValueChanged() {},
initialValue: '', initialValue: '',
label: '', label: '',
@ -53,81 +62,61 @@ export default class EditableText extends React.Component {
blurToSubmit: false, blurToSubmit: false,
}; };
constructor(props) { constructor(props: IProps) {
super(props); super(props);
// we track value as an JS object field rather than in React state this.state = {
// as React doesn't play nice with contentEditable. phase: Phases.Display,
this.value = ''; };
this.placeholder = false;
this._editable_div = createRef();
} }
state = {
phase: EditableText.Phases.Display,
};
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
UNSAFE_componentWillReceiveProps(nextProps) { public UNSAFE_componentWillReceiveProps(nextProps: IProps): void {
if (nextProps.initialValue !== this.props.initialValue) { if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue; this.value = nextProps.initialValue;
if (this._editable_div.current) { if (this.editableDiv.current) {
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
} }
} }
} }
componentDidMount() { public componentDidMount(): void {
this.value = this.props.initialValue; this.value = this.props.initialValue;
if (this._editable_div.current) { if (this.editableDiv.current) {
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
} }
} }
showPlaceholder = show => { private showPlaceholder = (show: boolean): void => {
if (show) { if (show) {
this._editable_div.current.textContent = this.props.placeholder; this.editableDiv.current.textContent = this.props.placeholder;
this._editable_div.current.setAttribute("class", this.props.className this.editableDiv.current.setAttribute("class", this.props.className
+ " " + this.props.placeholderClassName); + " " + this.props.placeholderClassName);
this.placeholder = true; this.placeholder = true;
this.value = ''; this.value = '';
} else { } else {
this._editable_div.current.textContent = this.value; this.editableDiv.current.textContent = this.value;
this._editable_div.current.setAttribute("class", this.props.className); this.editableDiv.current.setAttribute("class", this.props.className);
this.placeholder = false; this.placeholder = false;
} }
}; };
getValue = () => this.value; private cancelEdit = (): void => {
setValue = value => {
this.value = value;
this.showPlaceholder(!this.value);
};
edit = () => {
this.setState({ this.setState({
phase: EditableText.Phases.Edit, phase: Phases.Display,
});
};
cancelEdit = () => {
this.setState({
phase: EditableText.Phases.Display,
}); });
this.value = this.props.initialValue; this.value = this.props.initialValue;
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
this.onValueChanged(false); this.onValueChanged(false);
this._editable_div.current.blur(); this.editableDiv.current.blur();
}; };
onValueChanged = shouldSubmit => { private onValueChanged = (shouldSubmit: boolean): void => {
this.props.onValueChanged(this.value, shouldSubmit); this.props.onValueChanged(this.value, shouldSubmit);
}; };
onKeyDown = ev => { private onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>): void => {
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (this.placeholder) { if (this.placeholder) {
@ -142,13 +131,13 @@ export default class EditableText extends React.Component {
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
}; };
onKeyUp = ev => { private onKeyUp = (ev: React.KeyboardEvent<HTMLDivElement>): void => {
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (!ev.target.textContent) { if (!(ev.target as HTMLDivElement).textContent) {
this.showPlaceholder(true); this.showPlaceholder(true);
} else if (!this.placeholder) { } else if (!this.placeholder) {
this.value = ev.target.textContent; this.value = (ev.target as HTMLDivElement).textContent;
} }
if (ev.key === Key.ENTER) { if (ev.key === Key.ENTER) {
@ -160,22 +149,22 @@ export default class EditableText extends React.Component {
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
}; };
onClickDiv = ev => { private onClickDiv = (): void => {
if (!this.props.editable) return; if (!this.props.editable) return;
this.setState({ this.setState({
phase: EditableText.Phases.Edit, phase: Phases.Edit,
}); });
}; };
onFocus = ev => { private onFocus = (ev: React.FocusEvent<HTMLDivElement>): void => {
//ev.target.setSelectionRange(0, ev.target.textContent.length); //ev.target.setSelectionRange(0, ev.target.textContent.length);
const node = ev.target.childNodes[0]; const node = ev.target.childNodes[0];
if (node) { if (node) {
const range = document.createRange(); const range = document.createRange();
range.setStart(node, 0); range.setStart(node, 0);
range.setEnd(node, node.length); range.setEnd(node, ev.target.childNodes.length);
const sel = window.getSelection(); const sel = window.getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
@ -183,11 +172,15 @@ export default class EditableText extends React.Component {
} }
}; };
onFinish = (ev, shouldSubmit) => { private onFinish = (
ev: React.KeyboardEvent<HTMLDivElement> | React.FocusEvent<HTMLDivElement>,
shouldSubmit?: boolean,
): void => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this; const self = this;
const submit = (ev.key === Key.ENTER) || shouldSubmit; const submit = ("key" in ev && ev.key === Key.ENTER) || shouldSubmit;
this.setState({ this.setState({
phase: EditableText.Phases.Display, phase: Phases.Display,
}, () => { }, () => {
if (this.value !== this.props.initialValue) { if (this.value !== this.props.initialValue) {
self.onValueChanged(submit); self.onValueChanged(submit);
@ -195,7 +188,7 @@ export default class EditableText extends React.Component {
}); });
}; };
onBlur = ev => { private onBlur = (ev: React.FocusEvent<HTMLDivElement>): void => {
const sel = window.getSelection(); const sel = window.getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
@ -208,11 +201,11 @@ export default class EditableText extends React.Component {
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
}; };
render() { public render(): JSX.Element {
const { className, editable, initialValue, label, labelClassName } = this.props; const { className, editable, initialValue, label, labelClassName } = this.props;
let editableEl; let editableEl;
if (!editable || (this.state.phase === EditableText.Phases.Display && if (!editable || (this.state.phase === Phases.Display &&
(label || labelClassName) && !this.value) (label || labelClassName) && !this.value)
) { ) {
// show the label // show the label
@ -222,7 +215,7 @@ export default class EditableText extends React.Component {
} else { } else {
// show the content editable div, but manually manage its contents as react and contentEditable don't play nice together // show the content editable div, but manually manage its contents as react and contentEditable don't play nice together
editableEl = <div editableEl = <div
ref={this._editable_div} ref={this.editableDiv}
contentEditable={true} contentEditable={true}
className={className} className={className}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}