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 isArray from 'shared/isArray';
    
  11. 
    
  12. import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
    
  13. import {getToStringValue, toString} from './ToStringValue';
    
  14. import {disableTextareaChildren} from 'shared/ReactFeatureFlags';
    
  15. 
    
  16. let didWarnValDefaultVal = false;
    
  17. 
    
  18. /**
    
  19.  * Implements a <textarea> host component that allows setting `value`, and
    
  20.  * `defaultValue`. This differs from the traditional DOM API because value is
    
  21.  * usually set as PCDATA children.
    
  22.  *
    
  23.  * If `value` is not supplied (or null/undefined), user actions that affect the
    
  24.  * value will trigger updates to the element.
    
  25.  *
    
  26.  * If `value` is supplied (and not null/undefined), the rendered element will
    
  27.  * not trigger updates to the element. Instead, the `value` prop must change in
    
  28.  * order for the rendered element to be updated.
    
  29.  *
    
  30.  * The rendered element will be initialized with an empty value, the prop
    
  31.  * `defaultValue` if specified, or the children content (deprecated).
    
  32.  */
    
  33. 
    
  34. export function validateTextareaProps(element: Element, props: Object) {
    
  35.   if (__DEV__) {
    
  36.     if (
    
  37.       props.value !== undefined &&
    
  38.       props.defaultValue !== undefined &&
    
  39.       !didWarnValDefaultVal
    
  40.     ) {
    
  41.       console.error(
    
  42.         '%s contains a textarea with both value and defaultValue props. ' +
    
  43.           'Textarea elements must be either controlled or uncontrolled ' +
    
  44.           '(specify either the value prop, or the defaultValue prop, but not ' +
    
  45.           'both). Decide between using a controlled or uncontrolled textarea ' +
    
  46.           'and remove one of these props. More info: ' +
    
  47.           'https://reactjs.org/link/controlled-components',
    
  48.         getCurrentFiberOwnerNameInDevOrNull() || 'A component',
    
  49.       );
    
  50.       didWarnValDefaultVal = true;
    
  51.     }
    
  52.     if (props.children != null && props.value == null) {
    
  53.       console.error(
    
  54.         'Use the `defaultValue` or `value` props instead of setting ' +
    
  55.           'children on <textarea>.',
    
  56.       );
    
  57.     }
    
  58.   }
    
  59. }
    
  60. 
    
  61. export function updateTextarea(
    
  62.   element: Element,
    
  63.   value: ?string,
    
  64.   defaultValue: ?string,
    
  65. ) {
    
  66.   const node: HTMLTextAreaElement = (element: any);
    
  67.   if (value != null) {
    
  68.     // Cast `value` to a string to ensure the value is set correctly. While
    
  69.     // browsers typically do this as necessary, jsdom doesn't.
    
  70.     const newValue = toString(getToStringValue(value));
    
  71.     // To avoid side effects (such as losing text selection), only set value if changed
    
  72.     if (newValue !== node.value) {
    
  73.       node.value = newValue;
    
  74.     }
    
  75.     // TOOO: This should respect disableInputAttributeSyncing flag.
    
  76.     if (defaultValue == null) {
    
  77.       if (node.defaultValue !== newValue) {
    
  78.         node.defaultValue = newValue;
    
  79.       }
    
  80.       return;
    
  81.     }
    
  82.   }
    
  83.   if (defaultValue != null) {
    
  84.     node.defaultValue = toString(getToStringValue(defaultValue));
    
  85.   } else {
    
  86.     node.defaultValue = '';
    
  87.   }
    
  88. }
    
  89. 
    
  90. export function initTextarea(
    
  91.   element: Element,
    
  92.   value: ?string,
    
  93.   defaultValue: ?string,
    
  94.   children: ?string,
    
  95. ) {
    
  96.   const node: HTMLTextAreaElement = (element: any);
    
  97. 
    
  98.   let initialValue = value;
    
  99. 
    
  100.   // Only bother fetching default value if we're going to use it
    
  101.   if (initialValue == null) {
    
  102.     if (children != null) {
    
  103.       if (!disableTextareaChildren) {
    
  104.         if (defaultValue != null) {
    
  105.           throw new Error(
    
  106.             'If you supply `defaultValue` on a <textarea>, do not pass children.',
    
  107.           );
    
  108.         }
    
  109. 
    
  110.         if (isArray(children)) {
    
  111.           if (children.length > 1) {
    
  112.             throw new Error('<textarea> can only have at most one child.');
    
  113.           }
    
  114. 
    
  115.           children = children[0];
    
  116.         }
    
  117. 
    
  118.         defaultValue = children;
    
  119.       }
    
  120.     }
    
  121.     if (defaultValue == null) {
    
  122.       defaultValue = '';
    
  123.     }
    
  124.     initialValue = defaultValue;
    
  125.   }
    
  126. 
    
  127.   const stringValue = getToStringValue(initialValue);
    
  128.   node.defaultValue = (stringValue: any); // This will be toString:ed.
    
  129. 
    
  130.   // This is in postMount because we need access to the DOM node, which is not
    
  131.   // available until after the component has mounted.
    
  132.   const textContent = node.textContent;
    
  133. 
    
  134.   // Only set node.value if textContent is equal to the expected
    
  135.   // initial value. In IE10/IE11 there is a bug where the placeholder attribute
    
  136.   // will populate textContent as well.
    
  137.   // https://developer.microsoft.com/microsoft-edge/platform/issues/101525/
    
  138.   if (textContent === stringValue) {
    
  139.     if (textContent !== '' && textContent !== null) {
    
  140.       node.value = textContent;
    
  141.     }
    
  142.   }
    
  143. }
    
  144. 
    
  145. export function restoreControlledTextareaState(
    
  146.   element: Element,
    
  147.   props: Object,
    
  148. ) {
    
  149.   // DOM component is still mounted; update
    
  150.   updateTextarea(element, props.value, props.defaultValue);
    
  151. }