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 type {ReactEventResponderListener} from 'shared/ReactTypes';
  
  import React from 'react';
  import {DiscreteEvent} from 'shared/ReactTypes';
  import {isVirtualClick} from './shared';
  
  type KeyboardEventType =
    | 'keyboard:click'
    | 'keyboard:keydown'
    | 'keyboard:keyup';
  
  type KeyboardProps = {|
    disabled?: boolean,
    onClick?: (e: KeyboardEvent) => void,
    onKeyDown?: (e: KeyboardEvent) => void,
    onKeyUp?: (e: KeyboardEvent) => void,
  |};
  
  type KeyboardState = {|
    isActive: boolean,
  |};
  
  export type KeyboardEvent = {|
    altKey: boolean,
    ctrlKey: boolean,
    defaultPrevented: boolean,
    isComposing?: boolean,
    key?: string,
    metaKey: boolean,
    pointerType: 'keyboard',
    shiftKey: boolean,
    target: Element | Document,
    type: KeyboardEventType,
    timeStamp: number,
    continuePropagation: () => void,
    preventDefault: () => void,
  |};
  
  const targetEventTypes = ['click_active', 'keydown_active', 'keyup'];
  
  /**
   * Normalization of deprecated HTML5 `key` values
   * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
   */
  const normalizeKey = {
    Esc: 'Escape',
    Spacebar: ' ',
    Left: 'ArrowLeft',
    Up: 'ArrowUp',
    Right: 'ArrowRight',
    Down: 'ArrowDown',
    Del: 'Delete',
    Win: 'OS',
    Menu: 'ContextMenu',
    Apps: 'ContextMenu',
    Scroll: 'ScrollLock',
    MozPrintableKey: 'Unidentified',
  };
  
  /**
   * Translation from legacy `keyCode` to HTML5 `key`
   * Only special keys supported, all others depend on keyboard layout or browser
   * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
   */
  const translateToKey = {
    '8': 'Backspace',
    '9': 'Tab',
    '12': 'Clear',
    '13': 'Enter',
    '16': 'Shift',
    '17': 'Control',
    '18': 'Alt',
    '19': 'Pause',
    '20': 'CapsLock',
    '27': 'Escape',
    '32': ' ',
    '33': 'PageUp',
    '34': 'PageDown',
    '35': 'End',
    '36': 'Home',
    '37': 'ArrowLeft',
    '38': 'ArrowUp',
    '39': 'ArrowRight',
    '40': 'ArrowDown',
    '45': 'Insert',
    '46': 'Delete',
    '112': 'F1',
    '113': 'F2',
    '114': 'F3',
    '115': 'F4',
    '116': 'F5',
    '117': 'F6',
    '118': 'F7',
    '119': 'F8',
    '120': 'F9',
    '121': 'F10',
    '122': 'F11',
    '123': 'F12',
    '144': 'NumLock',
    '145': 'ScrollLock',
    '224': 'Meta',
  };
  
  function getEventKey(nativeEvent: Object): string {
    const nativeKey = nativeEvent.key;
    if (nativeKey) {
      // Normalize inconsistent values reported by browsers due to
      // implementations of a working draft specification.
  
      // FireFox implements `key` but returns `MozPrintableKey` for all
      // printable characters (normalized to `Unidentified`), ignore it.
      const key = normalizeKey[nativeKey] || nativeKey;
      if (key !== 'Unidentified') {
        return key;
      }
    }
    return translateToKey[nativeEvent.keyCode] || 'Unidentified';
  }
  
  function createKeyboardEvent(
    event: ReactDOMResponderEvent,
    context: ReactDOMResponderContext,
    type: KeyboardEventType,
  ): KeyboardEvent {
    const nativeEvent = (event: any).nativeEvent;
    const {altKey, ctrlKey, metaKey, shiftKey} = nativeEvent;
    let keyboardEvent = {
      altKey,
      ctrlKey,
      defaultPrevented: nativeEvent.defaultPrevented === true,
      metaKey,
      pointerType: 'keyboard',
      shiftKey,
      target: event.target,
      timeStamp: context.getTimeStamp(),
      type,
      // We don't use stopPropagation, as the default behavior
      // is to not propagate. Plus, there might be confusion
      // using stopPropagation as we don't actually stop
      // native propagation from working, but instead only
      // allow propagation to the others keyboard responders.
      continuePropagation() {
        context.continuePropagation();
      },
      preventDefault() {
        keyboardEvent.defaultPrevented = true;
        nativeEvent.preventDefault();
      },
    };
    if (type !== 'keyboard:click') {
      const key = getEventKey(nativeEvent);
      const isComposing = nativeEvent.isComposing;
      keyboardEvent = context.objectAssign({isComposing, key}, keyboardEvent);
    }
    return keyboardEvent;
  }
  
  function dispatchKeyboardEvent(
    event: ReactDOMResponderEvent,
    listener: KeyboardEvent => void,
    context: ReactDOMResponderContext,
    type: KeyboardEventType,
  ): void {
    const syntheticEvent = createKeyboardEvent(event, context, type);
    context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
  }
  
  const keyboardResponderImpl = {
    targetEventTypes,
    targetPortalPropagation: true,
    getInitialState(): KeyboardState {
      return {
        isActive: false,
      };
    },
    onEvent(
      event: ReactDOMResponderEvent,
      context: ReactDOMResponderContext,
      props: KeyboardProps,
      state: KeyboardState,
    ): void {
      const {type} = event;
  
      if (props.disabled) {
        return;
      }
  
      if (type === 'keydown') {
        state.isActive = true;
        const onKeyDown = props.onKeyDown;
        if (onKeyDown != null) {
          dispatchKeyboardEvent(
            event,
            ((onKeyDown: any): (e: KeyboardEvent) => void),
            context,
            'keyboard:keydown',
          );
        }
      } else if (type === 'click' && isVirtualClick(event)) {
        const onClick = props.onClick;
        if (onClick != null) {
          dispatchKeyboardEvent(event, onClick, context, 'keyboard:click');
        }
      } else if (type === 'keyup') {
        state.isActive = false;
        const onKeyUp = props.onKeyUp;
        if (onKeyUp != null) {
          dispatchKeyboardEvent(
            event,
            ((onKeyUp: any): (e: KeyboardEvent) => void),
            context,
            'keyboard:keyup',
          );
        }
      }
    },
  };
  
  export const KeyboardResponder = React.unstable_createResponder(
    'Keyboard',
    keyboardResponderImpl,
  );
  
  export function useKeyboard(
    props: KeyboardProps,
  ): ReactEventResponderListener<any, any> {
    return React.unstable_useResponder(KeyboardResponder, props);
  }
  