main
js 118 lines 2.97 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import * as React from 'react';
11 import {Fragment} from 'react';
12 import styles from './EditableValue.css';
13 import {useEditableValue} from '../hooks';
14
15 type OverrideValueFn = (path: Array<string | number>, value: any) => void;
16
17 type EditableValueProps = {
18 className?: string,
19 overrideValue: OverrideValueFn,
20 path: Array<string | number>,
21 value: any,
22 };
23
24 export default function EditableValue({
25 className = '',
26 overrideValue,
27 path,
28 value,
29 }: EditableValueProps): React.Node {
30 const [state, dispatch] = useEditableValue(value);
31 const {editableValue, hasPendingChanges, isValid, parsedValue} = state;
32
33 const reset = () =>
34 dispatch({
35 type: 'RESET',
36 externalValue: value,
37 });
38
39 // $FlowFixMe[missing-local-annot]
40 const handleChange = ({target}) =>
41 dispatch({
42 type: 'UPDATE',
43 editableValue: target.value,
44 externalValue: value,
45 });
46
47 // $FlowFixMe[missing-local-annot]
48 const handleCheckBoxToggle = ({target}) => {
49 dispatch({
50 type: 'UPDATE',
51 editableValue: target.checked,
52 externalValue: value,
53 });
54
55 // Unlike <input type="text"> which has both an onChange and an onBlur,
56 // <input type="checkbox"> updates state *and* applies changes in a single event.
57 // So we read from target.checked rather than parsedValue (which has not yet updated).
58 // We also don't check isValid (because that hasn't changed yet either);
59 // we don't need to check it anyway, since target.checked is always a boolean.
60 overrideValue(path, target.checked);
61 };
62
63 // $FlowFixMe[missing-local-annot]
64 const handleKeyDown = event => {
65 // Prevent keydown events from e.g. change selected element in the tree
66 event.stopPropagation();
67
68 switch (event.key) {
69 case 'Enter':
70 applyChanges();
71 break;
72 case 'Escape':
73 reset();
74 break;
75 default:
76 break;
77 }
78 };
79
80 const applyChanges = () => {
81 if (isValid && hasPendingChanges) {
82 overrideValue(path, parsedValue);
83 }
84 };
85
86 let placeholder = '';
87 if (editableValue === undefined) {
88 placeholder = '(undefined)';
89 } else {
90 placeholder = 'Enter valid JSON';
91 }
92
93 const isBool = parsedValue === true || parsedValue === false;
94
95 return (
96 <Fragment>
97 <input
98 autoComplete="new-password"
99 className={`${isValid ? styles.Input : styles.Invalid} ${className}`}
100 data-testname="EditableValue"
101 onBlur={applyChanges}
102 onChange={handleChange}
103 onKeyDown={handleKeyDown}
104 placeholder={placeholder}
105 type="text"
106 value={editableValue}
107 />
108 {isBool && (
109 <input
110 className={styles.Checkbox}
111 checked={parsedValue}
112 type="checkbox"
113 onChange={handleCheckBoxToggle}
114 />
115 )}
116 </Fragment>
117 );
118 }