content: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  import type {
    ReactDOMResponderEvent,
    ReactDOMResponderContext,
  } from 'shared/ReactDOMTypes';
  
  import React from 'react';
  import {DiscreteEvent} from 'shared/ReactTypes';
  import type {ReactEventResponderListener} from 'shared/ReactTypes';
  
  type InputEventType = 'change' | 'beforechange' | 'valuechange';
  
  type InputResponderProps = {
    disabled: boolean,
    onBeforeChange: (e: InputEvent) => void,
    onChange: (e: InputEvent) => void,
    onValueChange: (value: string | boolean) => void,
  };
  
  type InputEvent = {|
    data: string,
    isComposing: boolean,
    inputType: string,
    dataTransfer: null | string,
    target: Element | Document,
    type: InputEventType,
    timeStamp: number,
  |};
  
  const targetEventTypes = ['input', 'change', 'beforeinput', 'click'];
  
  const supportedInputTypes = new Set([
    'color',
    'date',
    'datetime',
    'datetime-local',
    'email',
    'month',
    'number',
    'password',
    'range',
    'search',
    'tel',
    'text',
    'time',
    'url',
    'week',
  ]);
  
  function isFunction(obj): boolean {
    return typeof obj === 'function';
  }
  
  function createInputEvent(
    event: ReactDOMResponderEvent,
    context: ReactDOMResponderContext,
    type: InputEventType,
    target: Document | Element,
  ): InputEvent {
    const {data, dataTransfer, inputType, isComposing} = (event: any).nativeEvent;
    return {
      data,
      dataTransfer,
      inputType,
      isComposing,
      target,
      timeStamp: context.getTimeStamp(),
      type,
    };
  }
  
  function dispatchInputEvent(
    event: ReactDOMResponderEvent,
    listener: InputEvent => void,
    context: ReactDOMResponderContext,
    type: InputEventType,
    target: Element | Document,
  ): void {
    const syntheticEvent = createInputEvent(event, context, type, target);
    context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
  }
  
  function getNodeName(elem: Element | Document): string {
    return elem.nodeName && elem.nodeName.toLowerCase();
  }
  
  function isTextInputElement(elem: Element | Document): boolean {
    const nodeName = getNodeName(elem);
    const type = (elem: any).type;
    return (
      nodeName === 'textarea' ||
      (nodeName === 'input' && (type == null || supportedInputTypes.has(type)))
    );
  }
  
  function isCheckable(elem: Element | Document): boolean {
    const nodeName = getNodeName(elem);
    const type = (elem: any).type;
    return nodeName === 'input' && (type === 'checkbox' || type === 'radio');
  }
  
  function shouldUseChangeEvent(elem: Element | Document): boolean {
    const nodeName = getNodeName(elem);
    return (
      nodeName === 'select' ||
      (nodeName === 'input' && (elem: any).type === 'file')
    );
  }
  
  function dispatchChangeEvent(
    context: ReactDOMResponderContext,
    props: InputResponderProps,
    target: Element | Document,
  ): void {
    const onValueChange = props.onValueChange;
    if (isFunction(onValueChange)) {
      const value = getValueFromNode(target);
      context.dispatchEvent(value, onValueChange, DiscreteEvent);
    }
  }
  
  function dispatchBothChangeEvents(
    event: ReactDOMResponderEvent,
    context: ReactDOMResponderContext,
    props: InputResponderProps,
    target: Document | Element,
  ): void {
    context.enqueueStateRestore(target);
    const onChange = props.onChange;
    if (isFunction(onChange)) {
      dispatchInputEvent(event, onChange, context, 'change', target);
    }
    dispatchChangeEvent(context, props, target);
  }
  
  function updateValueIfChanged(elem: Element | Document): boolean {
    // React's internal value tracker
    const valueTracker = (elem: any)._valueTracker;
    if (valueTracker == null) {
      return true;
    }
    const prevValue = valueTracker.getValue();
    const nextValue = getValueFromNode(elem);
  
    if (prevValue !== nextValue) {
      valueTracker.setValue(nextValue);
      return true;
    }
    return false;
  }
  
  function getValueFromNode(node: Element | Document): string {
    let value = '';
    if (!node) {
      return value;
    }
  
    if (isCheckable(node)) {
      value = (node: any).checked ? 'true' : 'false';
    } else {
      value = (node: any).value;
    }
  
    return value;
  }
  
  const inputResponderImpl = {
    targetEventTypes,
    onEvent(
      event: ReactDOMResponderEvent,
      context: ReactDOMResponderContext,
      props: InputResponderProps,
    ): void {
      const {type, target} = event;
  
      if (props.disabled) {
        return;
      }
      const currentTarget = context.getResponderNode();
      if (target !== currentTarget || currentTarget === null) {
        return;
      }
      switch (type) {
        default: {
          if (shouldUseChangeEvent(target) && type === 'change') {
            dispatchBothChangeEvents(event, context, props, currentTarget);
          } else if (
            isTextInputElement(target) &&
            (type === 'input' || type === 'change') &&
            updateValueIfChanged(target)
          ) {
            dispatchBothChangeEvents(event, context, props, currentTarget);
          } else if (
            isCheckable(target) &&
            type === 'click' &&
            updateValueIfChanged(target)
          ) {
            dispatchBothChangeEvents(event, context, props, currentTarget);
          }
          break;
        }
      }
    },
  };
  
  export const InputResponder = React.unstable_createResponder(
    'Input',
    inputResponderImpl,
  );
  
  export function useInput(
    props: InputResponderProps,
  ): ReactEventResponderListener<any, any> {
    return React.unstable_useResponder(InputResponder, props);
  }
  