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. }