Files
react/packages/react-interactions/events/src/dom/Input.js
T
Sebastian Markbåge c5d2fc7127 Move some files out of /shared and rename to upper case (#18363)
* Rename lower case isomorphic default exports modules to upper case named exports

We're somewhat inconsistent here between e.g. ReactLazy and memo.

Let's pick one.

This also moves the responder, fundamental, scope creators from shared
since they're isomorphic and same as the other creators.

* Move some files that are specific to the react-reconciler from shared

Individual renderers are allowed to deep require into the reconciler.

* Move files specific to react-dom from shared

react-interactions is right now dom specific (it wasn't before) so we can
type check it together with other dom stuff. Avoids the need for
a shared ReactDOMTypes to be checked by RN for example.

* Move ReactWorkTags to the reconciler

* Move createPortal to export from reconciler

Otherwise Noop can't access it since it's not allowed deep requires.
2020-03-21 15:22:01 -07:00

226 lines
5.5 KiB
JavaScript

/**
* 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 'react-dom/src/shared/ReactDOMTypes';
import * as 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;
}
}
},
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const InputResponder = React.DEPRECATED_createResponder(
'Input',
inputResponderImpl,
);
export function useInput(
props: InputResponderProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(InputResponder, props);
}