Remove the deprecated React Flare event system (#19520)

This commit is contained in:
Dominic Gannaway 2020-08-05 15:13:29 +01:00 committed by GitHub
parent 8d57ca519a
commit b61174fb7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 125 additions and 12325 deletions

View File

@ -10,10 +10,6 @@ import Mode from 'art/modes/current';
import invariant from 'shared/invariant';
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
import type {
ReactEventResponder,
ReactEventResponderInstance,
} from 'shared/ReactTypes';
const pooledTransform = new Transform();
@ -429,22 +425,6 @@ export function clearContainer(container) {
// TODO Implement this
}
export function DEPRECATED_mountResponderInstance(
responder: ReactEventResponder<any, any>,
responderInstance: ReactEventResponderInstance<any, any>,
props: Object,
state: Object,
instance: Object,
) {
throw new Error('Not yet implemented.');
}
export function DEPRECATED_unmountResponderInstance(
responderInstance: ReactEventResponderInstance<any, any>,
): void {
throw new Error('Not yet implemented.');
}
export function getFundamentalComponentInstance(fundamentalInstance) {
throw new Error('Not yet implemented.');
}

View File

@ -13,8 +13,6 @@ import type {
MutableSourceSubscribeFn,
ReactContext,
ReactProviderType,
ReactEventResponder,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import type {
Fiber,
@ -260,22 +258,6 @@ function useMutableSource<Source, Snapshot>(
return value;
}
function useResponder(
responder: ReactEventResponder<any, any>,
listenerProps: Object,
): ReactEventResponderListener<any, any> {
// Don't put the actual event responder object in, just its displayName
const value = {
responder: responder.displayName || 'EventResponder',
props: listenerProps,
};
hookLog.push({primitive: 'Responder', stackError: new Error(), value});
return {
responder,
props: listenerProps,
};
}
function useTransition(
config: SuspenseConfig | null | void,
): [(() => void) => void, boolean] {
@ -335,7 +317,6 @@ const Dispatcher: DispatcherType = {
useReducer,
useRef,
useState,
useResponder,
useTransition,
useMutableSource,
useDeferredValue,

View File

@ -1,46 +0,0 @@
/**
* 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.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
let React;
let ReactDebugTools;
describe('ReactHooksInspection', () => {
beforeEach(() => {
jest.resetModules();
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDebugTools = require('react-debug-tools');
});
// @gate experimental
it('should inspect a simple useResponder hook', () => {
const TestResponder = React.DEPRECATED_createResponder('TestResponder', {});
function Foo(props) {
const listener = React.DEPRECATED_useResponder(TestResponder, {
preventDefault: false,
});
return <div DEPRECATED_flareListeners={listener}>Hello world</div>;
}
const tree = ReactDebugTools.inspectHooks(Foo, {});
expect(tree).toEqual([
{
isStateEditable: false,
id: 0,
name: 'Responder',
value: {props: {preventDefault: false}, responder: 'TestResponder'},
subHooks: [],
},
]);
});
});

View File

@ -58,9 +58,6 @@ export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)';
export const PROVIDER_NUMBER = 0xeacd;
export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)';
export const RESPONDER_NUMBER = 0xead6;
export const RESPONDER_SYMBOL_STRING = 'Symbol(react.responder)';
export const SCOPE_NUMBER = 0xead7;
export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)';

View File

@ -17,7 +17,6 @@ let ReactFeatureFlags;
let Suspense;
let SuspenseList;
let act;
let useHover;
function dispatchMouseEvent(to, from) {
if (!to) {
@ -76,7 +75,7 @@ describe('ReactDOMServerPartialHydration', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSuspenseCallback = true;
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
ReactFeatureFlags.enableCreateEventHandleAPI = true;
React = require('react');
ReactDOM = require('react-dom');
@ -2202,8 +2201,10 @@ describe('ReactDOMServerPartialHydration', () => {
});
// @gate experimental
it('does not invoke an event on a hydrated EventResponder until it commits', async () => {
it('does not invoke an event on a hydrated event handle until it commits', async () => {
const setClick = ReactDOM.unstable_createEventHandle('click');
let suspend = false;
let isServerRendering = true;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
@ -2216,17 +2217,15 @@ describe('ReactDOMServerPartialHydration', () => {
}
const onEvent = jest.fn();
const TestResponder = React.DEPRECATED_createResponder(
'TestEventResponder',
{
targetEventTypes: ['click'],
onEvent,
},
);
function Button() {
const listener = React.DEPRECATED_useResponder(TestResponder, {});
return <a DEPRECATED_flareListeners={listener}>Click me</a>;
const ref = React.useRef(null);
if (!isServerRendering) {
React.useLayoutEffect(() => {
return setClick(ref.current, onEvent);
});
}
return <a ref={ref}>Click me</a>;
}
function App() {
@ -2253,6 +2252,7 @@ describe('ReactDOMServerPartialHydration', () => {
// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend = true;
isServerRendering = false;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
@ -2364,23 +2364,25 @@ describe('ReactDOMServerPartialHydration', () => {
});
// @gate experimental
it('invokes discrete events on nested suspense boundaries in a root (responder system)', async () => {
it('invokes discrete events on nested suspense boundaries in a root (createEventHandle)', async () => {
let suspend = false;
let isServerRendering = true;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
const onEvent = jest.fn();
const TestResponder = React.DEPRECATED_createResponder(
'TestEventResponder',
{
targetEventTypes: ['click'],
onEvent,
},
);
const setClick = ReactDOM.unstable_createEventHandle('click');
function Button() {
const listener = React.DEPRECATED_useResponder(TestResponder, {});
return <a DEPRECATED_flareListeners={listener}>Click me</a>;
const ref = React.useRef(null);
if (!isServerRendering) {
React.useLayoutEffect(() => {
return setClick(ref.current, onEvent);
});
}
return <a ref={ref}>Click me</a>;
}
function Child() {
@ -2416,6 +2418,7 @@ describe('ReactDOMServerPartialHydration', () => {
// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend = true;
isServerRendering = false;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
@ -2704,124 +2707,6 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('blocks only on the last continuous event (Responder system)', async () => {
useHover = require('react-interactions/events/hover').useHover;
let suspend1 = false;
let resolve1;
const promise1 = new Promise(resolvePromise => (resolve1 = resolvePromise));
let suspend2 = false;
let resolve2;
const promise2 = new Promise(resolvePromise => (resolve2 = resolvePromise));
function First({text}) {
if (suspend1) {
throw promise1;
} else {
return 'Hello';
}
}
function Second({text}) {
if (suspend2) {
throw promise2;
} else {
return 'World';
}
}
const ops = [];
function App() {
const listener1 = useHover({
onHoverStart() {
ops.push('Hover Start First');
},
onHoverEnd() {
ops.push('Hover End First');
},
});
const listener2 = useHover({
onHoverStart() {
ops.push('Hover Start Second');
},
onHoverEnd() {
ops.push('Hover End Second');
},
});
return (
<div>
<Suspense fallback="Loading First...">
<span DEPRECATED_flareListeners={listener1} />
{/* We suspend after to test what happens when we eager
attach the listener. */}
<First />
</Suspense>
<Suspense fallback="Loading Second...">
<span DEPRECATED_flareListeners={listener2}>
<Second />
</span>
</Suspense>
</div>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
const appDiv = container.getElementsByTagName('div')[0];
const firstSpan = appDiv.getElementsByTagName('span')[0];
const secondSpan = appDiv.getElementsByTagName('span')[1];
expect(firstSpan.textContent).toBe('');
expect(secondSpan.textContent).toBe('World');
// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend1 = true;
suspend2 = true;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
dispatchMouseEvent(appDiv, null);
dispatchMouseEvent(firstSpan, appDiv);
dispatchMouseEvent(secondSpan, firstSpan);
// Neither target is yet hydrated.
expect(ops).toEqual([]);
// Resolving the second promise so that rendering can complete.
suspend2 = false;
resolve2();
await promise2;
Scheduler.unstable_flushAll();
jest.runAllTimers();
// We've unblocked the current hover target so we should be
// able to replay it now.
expect(ops).toEqual(['Hover Start Second']);
// Resolving the first promise has no effect now.
suspend1 = false;
resolve1();
await promise1;
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ops).toEqual(['Hover Start Second']);
document.body.removeChild(container);
});
// @gate experimental
it('finishes normal pri work before continuing to hydrate a retry', async () => {
let suspend = false;

View File

@ -96,8 +96,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
jest.resetModuleRegistry();
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
ReactFeatureFlags.enableCreateEventHandleAPI = true;
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
@ -348,18 +347,22 @@ describe('ReactDOMServerSelectiveHydration', () => {
});
// @gate experimental
it('hydrates the target boundary synchronously during a click (flare)', async () => {
const usePress = require('react-interactions/events/press-legacy').usePress;
it('hydrates the target boundary synchronously during a click (createEventHandle)', async () => {
const setClick = ReactDOM.unstable_createEventHandle('click');
let isServerRendering = true;
function Child({text}) {
const ref = React.useRef(null);
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
if (!isServerRendering) {
React.useLayoutEffect(() => {
return setClick(ref.current, () => {
Scheduler.unstable_yieldValue('Clicked ' + text);
});
});
}
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
return <span ref={ref}>{text}</span>;
}
function App() {
@ -386,7 +389,10 @@ describe('ReactDOMServerSelectiveHydration', () => {
container.innerHTML = finalHTML;
isServerRendering = false;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// Nothing has been hydrated so far.
@ -398,11 +404,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
// This should synchronously hydrate the root App and the second suspense
// boundary.
const preventDefault = jest.fn();
target.virtualclick({preventDefault});
// The event should have been canceled because we called preventDefault.
expect(preventDefault).toHaveBeenCalled();
target.virtualclick();
// We rendered App, B and then invoked the event without rendering A.
expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']);
@ -414,24 +416,29 @@ describe('ReactDOMServerSelectiveHydration', () => {
});
// @gate experimental
it('hydrates at higher pri if sync did not work first time (flare)', async () => {
const usePress = require('react-interactions/events/press-legacy').usePress;
it('hydrates at higher pri if sync did not work first time (createEventHandle)', async () => {
let suspend = false;
let isServerRendering = true;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
const setClick = ReactDOM.unstable_createEventHandle('click');
function Child({text}) {
const ref = React.useRef(null);
if ((text === 'A' || text === 'D') && suspend) {
throw promise;
}
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
if (!isServerRendering) {
React.useLayoutEffect(() => {
return setClick(ref.current, () => {
Scheduler.unstable_yieldValue('Clicked ' + text);
});
});
}
return <span ref={ref}>{text}</span>;
}
function App() {
@ -467,6 +474,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
const spanD = container.getElementsByTagName('span')[3];
suspend = true;
isServerRendering = false;
// A and D will be suspended. We'll click on D which should take
// priority, after we unsuspend.
@ -496,24 +504,28 @@ describe('ReactDOMServerSelectiveHydration', () => {
});
// @gate experimental
it('hydrates at higher pri for secondary discrete events (flare)', async () => {
const usePress = require('react-interactions/events/press-legacy').usePress;
it('hydrates at higher pri for secondary discrete events (createEventHandle)', async () => {
const setClick = ReactDOM.unstable_createEventHandle('click');
let suspend = false;
let isServerRendering = true;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
function Child({text}) {
const ref = React.useRef(null);
if ((text === 'A' || text === 'D') && suspend) {
throw promise;
}
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
if (!isServerRendering) {
React.useLayoutEffect(() => {
return setClick(ref.current, () => {
Scheduler.unstable_yieldValue('Clicked ' + text);
});
});
}
return <span ref={ref}>{text}</span>;
}
function App() {
@ -551,6 +563,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
const spanD = container.getElementsByTagName('span')[3];
suspend = true;
isServerRendering = false;
// A and D will be suspended. We'll click on D which should take
// priority, after we unsuspend.

View File

@ -7,8 +7,6 @@
* @flow
*/
import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
import {
registrationNameDependencies,
possibleRegistrationNames,
@ -16,11 +14,6 @@ import {
import {canUseDOM} from 'shared/ExecutionEnvironment';
import invariant from 'shared/invariant';
import {
setListenToResponderEventTypes,
addResponderEventSystemEvent,
removeTrappedEventListener,
} from '../events/DeprecatedDOMEventResponderSystem';
import {
getValueForAttribute,
@ -81,16 +74,12 @@ import {validateProperties as validateInputProperties} from '../shared/ReactDOMN
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
import {
enableDeprecatedFlareAPI,
enableTrustedTypesIntegration,
} from 'shared/ReactFeatureFlags';
import {enableTrustedTypesIntegration} from 'shared/ReactFeatureFlags';
import {
listenToReactEvent,
mediaEventTypes,
listenToNonDelegatedEvent,
} from '../events/DOMPluginEventSystem';
import {getEventListenerMap} from './ReactDOMComponentTree';
let didWarnInvalidHydration = false;
let didWarnScriptTags = false;
@ -102,7 +91,6 @@ const AUTOFOCUS = 'autoFocus';
const CHILDREN = 'children';
const STYLE = 'style';
const HTML = '__html';
const DEPRECATED_flareListeners = 'DEPRECATED_flareListeners';
const {html: HTML_NAMESPACE} = Namespaces;
@ -358,7 +346,6 @@ function setInitialDOMProperties(
setTextContent(domElement, '' + nextProp);
}
} else if (
(enableDeprecatedFlareAPI && propKey === DEPRECATED_flareListeners) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
@ -728,7 +715,6 @@ export function diffProperties(
} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
// Noop. This is handled by the clear text mechanism.
} else if (
(enableDeprecatedFlareAPI && propKey === DEPRECATED_flareListeners) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
@ -817,7 +803,6 @@ export function diffProperties(
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
} else if (
(enableDeprecatedFlareAPI && propKey === DEPRECATED_flareListeners) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
@ -1083,7 +1068,6 @@ export function diffHydratedProperties(
if (suppressHydrationWarning) {
// Don't bother comparing. We're ignoring all these warnings.
} else if (
(enableDeprecatedFlareAPI && propKey === DEPRECATED_flareListeners) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING ||
// Controlled attributes are not validated
@ -1317,71 +1301,3 @@ export function restoreControlledState(
return;
}
}
function endsWith(subject: string, search: string): boolean {
const length = subject.length;
return subject.substring(length - search.length, length) === search;
}
export function listenToEventResponderEventTypes(
eventTypes: Array<string>,
document: Document,
): void {
if (enableDeprecatedFlareAPI) {
// Get the listening Map for this element. We use this to track
// what events we're listening to.
const listenerMap = getEventListenerMap(document);
// Go through each target event type of the event responder
for (let i = 0, length = eventTypes.length; i < length; ++i) {
const eventType = eventTypes[i];
const isPassive = !endsWith(eventType, '_active');
const eventKey = isPassive ? eventType + '_passive' : eventType;
const targetEventType = isPassive
? eventType
: eventType.substring(0, eventType.length - 7);
if (!listenerMap.has(eventKey)) {
if (isPassive) {
const activeKey = targetEventType + '_active';
// If we have an active event listener, do not register
// a passive event listener. We use the same active event
// listener.
if (listenerMap.has(activeKey)) {
continue;
}
} else {
// If we have a passive event listener, remove the
// existing passive event listener before we add the
// active event listener.
const passiveKey = targetEventType + '_passive';
const passiveItem = ((listenerMap.get(
passiveKey,
): any): ElementListenerMapEntry | void);
if (passiveItem !== undefined) {
removeTrappedEventListener(
document,
(targetEventType: any),
true,
passiveItem.listener,
);
listenerMap.delete(passiveKey);
}
}
const eventListener = addResponderEventSystemEvent(
document,
targetEventType,
isPassive,
);
listenerMap.set(eventKey, {
passive: isPassive,
listener: eventListener,
});
}
}
}
}
// We can remove this once the event API is stable and out of a flag
if (enableDeprecatedFlareAPI) {
setListenToResponderEventTypes(listenToEventResponderEventTypes);
}

View File

@ -28,10 +28,7 @@ import {
} from '../events/DOMPluginEventSystem';
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
import {
PLUGIN_EVENT_SYSTEM,
IS_EVENT_HANDLE_NON_MANAGED_NODE,
} from '../events/EventSystemFlags';
import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../events/EventSystemFlags';
import {
enableScopeAPI,
@ -164,7 +161,7 @@ function registerReactDOMEvent(
null,
isPassiveListener,
listenerPriority,
PLUGIN_EVENT_SYSTEM | IS_EVENT_HANDLE_NON_MANAGED_NODE,
IS_EVENT_HANDLE_NON_MANAGED_NODE,
);
} else {
invariant(

View File

@ -16,11 +16,7 @@ import type {
} from 'react-reconciler/src/ReactTestSelectors';
import type {RootType} from './ReactDOMRoot';
import type {ReactScopeInstance} from 'shared/ReactTypes';
import type {
ReactDOMEventResponder,
ReactDOMEventResponderInstance,
ReactDOMFundamentalComponentInstance,
} from '../shared/ReactDOMTypes';
import type {ReactDOMFundamentalComponentInstance} from '../shared/ReactDOMTypes';
import {
precacheFiberNode,
@ -45,7 +41,6 @@ import {
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
warnForInsertedHydratedText,
listenToEventResponderEventTypes,
} from './ReactDOMComponent';
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
import setTextContent from './setTextContent';
@ -65,15 +60,10 @@ import {
import dangerousStyleValue from '../shared/dangerousStyleValue';
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
import {
mountEventResponder,
unmountEventResponder,
} from '../events/DeprecatedDOMEventResponderSystem';
import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
import {
enableSuspenseServerRenderer,
enableDeprecatedFlareAPI,
enableFundamentalAPI,
enableCreateEventHandleAPI,
enableScopeAPI,
@ -232,7 +222,7 @@ export function prepareForCommit(containerInfo: Container): Object | null {
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
selectionInformation = getSelectionInformation();
let activeInstance = null;
if (enableDeprecatedFlareAPI || enableCreateEventHandleAPI) {
if (enableCreateEventHandleAPI) {
const focusedElem = selectionInformation.focusedElem;
if (focusedElem !== null) {
activeInstance = getClosestInstanceFromNode(focusedElem);
@ -243,7 +233,7 @@ export function prepareForCommit(containerInfo: Container): Object | null {
}
export function beforeActiveInstanceBlur(): void {
if (enableDeprecatedFlareAPI || enableCreateEventHandleAPI) {
if (enableCreateEventHandleAPI) {
ReactBrowserEventEmitterSetEnabled(true);
dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem);
ReactBrowserEventEmitterSetEnabled(false);
@ -251,7 +241,7 @@ export function beforeActiveInstanceBlur(): void {
}
export function afterActiveInstanceBlur(): void {
if (enableDeprecatedFlareAPI || enableCreateEventHandleAPI) {
if (enableCreateEventHandleAPI) {
ReactBrowserEventEmitterSetEnabled(true);
dispatchAfterDetachedBlur((selectionInformation: any).focusedElem);
ReactBrowserEventEmitterSetEnabled(false);
@ -510,7 +500,7 @@ function createEvent(type: DOMEventName, bubbles: boolean): Event {
}
function dispatchBeforeDetachedBlur(target: HTMLElement): void {
if (enableDeprecatedFlareAPI || enableCreateEventHandleAPI) {
if (enableCreateEventHandleAPI) {
const event = createEvent('beforeblur', true);
// Dispatch "beforeblur" directly on the target,
// so it gets picked up by the event system and
@ -520,7 +510,7 @@ function dispatchBeforeDetachedBlur(target: HTMLElement): void {
}
function dispatchAfterDetachedBlur(target: HTMLElement): void {
if (enableDeprecatedFlareAPI || enableCreateEventHandleAPI) {
if (enableCreateEventHandleAPI) {
const event = createEvent('afterblur', false);
// So we know what was detached, make the relatedTarget the
// detached target on the "afterblur" event.
@ -975,37 +965,6 @@ export function didNotFindHydratableSuspenseInstance(
}
}
export function DEPRECATED_mountResponderInstance(
responder: ReactDOMEventResponder,
responderInstance: ReactDOMEventResponderInstance,
responderProps: Object,
responderState: Object,
instance: Instance,
): ReactDOMEventResponderInstance {
// Listen to events
const doc = instance.ownerDocument;
const {targetEventTypes} = ((responder: any): ReactDOMEventResponder);
if (targetEventTypes !== null) {
listenToEventResponderEventTypes(targetEventTypes, doc);
}
mountEventResponder(
responder,
responderInstance,
responderProps,
responderState,
);
return responderInstance;
}
export function DEPRECATED_unmountResponderInstance(
responderInstance: ReactDOMEventResponderInstance,
): void {
if (enableDeprecatedFlareAPI) {
// TODO stop listening to targetEventTypes
unmountEventResponder(responderInstance);
}
}
export function getFundamentalComponentInstance(
fundamentalInstance: ReactDOMFundamentalComponentInstance,
): Instance {

View File

@ -22,7 +22,6 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {registrationNameDependencies} from './EventRegistry';
import {
PLUGIN_EVENT_SYSTEM,
IS_CAPTURE_PHASE,
IS_EVENT_HANDLE_NON_MANAGED_NODE,
IS_NON_DELEGATED,
@ -64,7 +63,6 @@ import {
addEventBubbleListenerWithPassiveFlag,
addEventCaptureListenerWithPassiveFlag,
} from './EventListener';
import {removeTrappedEventListener} from './DeprecatedDOMEventResponderSystem';
import {topLevelEventsToReactNames} from './DOMEventProperties';
import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';
import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
@ -318,7 +316,7 @@ export function listenToNonDelegatedEvent(
const listener = addTrappedEventListener(
targetElement,
domEventName,
PLUGIN_EVENT_SYSTEM | IS_NON_DELEGATED,
IS_NON_DELEGATED,
isCapturePhaseListener,
);
listenerMap.set(listenerMapKey, {passive: false, listener});
@ -332,7 +330,7 @@ export function listenToNativeEvent(
targetElement: Element | null,
isPassiveListener?: boolean,
listenerPriority?: EventPriority,
eventSystemFlags?: EventSystemFlags = PLUGIN_EVENT_SYSTEM,
eventSystemFlags?: EventSystemFlags = 0,
): void {
let target = rootContainerElement;
// selectionchange needs to be attached to the document
@ -381,11 +379,11 @@ export function listenToNativeEvent(
// If we should upgrade, then we need to remove the existing trapped
// event listener for the target container.
if (shouldUpgrade) {
removeTrappedEventListener(
removeEventListener(
target,
domEventName,
isCapturePhaseListener,
((listenerEntry: any): ElementListenerMapEntry).listener,
isCapturePhaseListener,
);
}
if (isCapturePhaseListener) {
@ -549,7 +547,7 @@ function deferClickToDocumentForLegacyFBSupport(
addTrappedEventListener(
targetContainer,
domEventName,
PLUGIN_EVENT_SYSTEM | IS_LEGACY_FB_SUPPORT_MODE,
IS_LEGACY_FB_SUPPORT_MODE,
false,
isDeferredListenerForLegacyFBSupport,
);

View File

@ -1,642 +0,0 @@
/**
* 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 EventSystemFlags,
IS_PASSIVE,
PASSIVE_NOT_SUPPORTED,
RESPONDER_EVENT_SYSTEM,
} from './EventSystemFlags';
import type {AnyNativeEvent} from '../events/PluginModuleType';
import {
HostComponent,
ScopeComponent,
HostPortal,
} from 'react-reconciler/src/ReactWorkTags';
import type {EventPriority} from 'shared/ReactTypes';
import type {
ReactDOMEventResponder,
ReactDOMEventResponderInstance,
ReactDOMResponderContext,
ReactDOMResponderEvent,
} from '../shared/ReactDOMTypes';
import type {DOMEventName} from '../events/DOMEventNames';
import {
batchedEventUpdates,
discreteUpdates,
flushDiscreteUpdatesIfNeeded,
executeUserEventHandler,
} from './ReactDOMUpdateBatching';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
import {enqueueStateRestore} from './ReactDOMControlledComponent';
import {createEventListenerWrapper} from './ReactDOMEventListener';
import {passiveBrowserEventsSupported} from './checkPassiveEvents';
import {
addEventCaptureListener,
addEventCaptureListenerWithPassiveFlag,
removeEventListener,
} from './EventListener';
import {
ContinuousEvent,
UserBlockingEvent,
DiscreteEvent,
} from 'shared/ReactTypes';
// Intentionally not named imports because Rollup would use dynamic dispatch for
// CommonJS interop named imports.
import * as Scheduler from 'scheduler';
import {
InputContinuousLanePriority,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
} from 'react-reconciler/src/ReactFiberLane';
const {
unstable_UserBlockingPriority: UserBlockingPriority,
unstable_runWithPriority: runWithPriority,
} = Scheduler;
export let listenToResponderEventTypesImpl;
export function setListenToResponderEventTypes(
_listenToResponderEventTypesImpl: Function,
) {
listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl;
}
const rootEventTypesToEventResponderInstances: Map<
DOMEventName | string,
Set<ReactDOMEventResponderInstance>,
> = new Map();
type PropagationBehavior = 0 | 1;
const DoNotPropagateToNextResponder = 0;
const PropagateToNextResponder = 1;
let currentTimeStamp = 0;
let currentInstance: null | ReactDOMEventResponderInstance = null;
let currentDocument: null | Document = null;
let currentPropagationBehavior: PropagationBehavior = DoNotPropagateToNextResponder;
const eventResponderContext: ReactDOMResponderContext = {
dispatchEvent(
eventValue: any,
eventListener: any => void,
eventPriority: EventPriority,
): void {
validateResponderContext();
validateEventValue(eventValue);
switch (eventPriority) {
case DiscreteEvent: {
flushDiscreteUpdatesIfNeeded(currentTimeStamp);
discreteUpdates(() =>
executeUserEventHandler(eventListener, eventValue),
);
break;
}
case UserBlockingEvent: {
const previousPriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(InputContinuousLanePriority);
runWithPriority(UserBlockingPriority, () =>
executeUserEventHandler(eventListener, eventValue),
);
} finally {
setCurrentUpdateLanePriority(previousPriority);
}
break;
}
case ContinuousEvent: {
executeUserEventHandler(eventListener, eventValue);
break;
}
}
},
isTargetWithinResponder(target: null | Element | Document): boolean {
validateResponderContext();
if (target != null) {
let fiber = getClosestInstanceFromNode(target);
const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
.fiber;
while (fiber !== null) {
if (fiber === responderFiber || fiber.alternate === responderFiber) {
return true;
}
fiber = fiber.return;
}
}
return false;
},
isTargetWithinResponderScope(target: null | Element | Document): boolean {
validateResponderContext();
const componentInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
const responder = componentInstance.responder;
if (target != null) {
let fiber = getClosestInstanceFromNode(target);
const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
.fiber;
while (fiber !== null) {
if (fiber === responderFiber || fiber.alternate === responderFiber) {
return true;
}
if (doesFiberHaveResponder(fiber, responder)) {
return false;
}
fiber = fiber.return;
}
}
return false;
},
isTargetWithinNode(
childTarget: Element | Document,
parentTarget: Element | Document,
): boolean {
validateResponderContext();
const childFiber = getClosestInstanceFromNode(childTarget);
const parentFiber = getClosestInstanceFromNode(parentTarget);
if (childFiber != null && parentFiber != null) {
const parentAlternateFiber = parentFiber.alternate;
let node = childFiber;
while (node !== null) {
if (node === parentFiber || node === parentAlternateFiber) {
return true;
}
node = node.return;
}
return false;
}
// Fallback to DOM APIs
return parentTarget.contains(childTarget);
},
addRootEventTypes(rootEventTypes: Array<string>): void {
validateResponderContext();
listenToResponderEventTypesImpl(rootEventTypes, currentDocument);
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const eventResponderInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
DEPRECATED_registerRootEventType(rootEventType, eventResponderInstance);
}
},
removeRootEventTypes(rootEventTypes: Array<string>): void {
validateResponderContext();
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const rootEventResponders = rootEventTypesToEventResponderInstances.get(
rootEventType,
);
const rootEventTypesSet = ((currentInstance: any): ReactDOMEventResponderInstance)
.rootEventTypes;
if (rootEventTypesSet !== null) {
rootEventTypesSet.delete(rootEventType);
}
if (rootEventResponders !== undefined) {
rootEventResponders.delete(
((currentInstance: any): ReactDOMEventResponderInstance),
);
}
}
},
getActiveDocument,
objectAssign: Object.assign,
getTimeStamp(): number {
validateResponderContext();
return currentTimeStamp;
},
isTargetWithinHostComponent(
target: Element | Document,
elementType: string,
): boolean {
validateResponderContext();
let fiber = getClosestInstanceFromNode(target);
while (fiber !== null) {
if (fiber.tag === HostComponent && fiber.type === elementType) {
return true;
}
fiber = fiber.return;
}
return false;
},
continuePropagation() {
currentPropagationBehavior = PropagateToNextResponder;
},
enqueueStateRestore,
getResponderNode(): Element | null {
validateResponderContext();
const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
.fiber;
if (responderFiber.tag === ScopeComponent) {
return null;
}
return responderFiber.stateNode;
},
};
function validateEventValue(eventValue: any): void {
if (typeof eventValue === 'object' && eventValue !== null) {
const {target, type, timeStamp} = eventValue;
if (target == null || type == null || timeStamp == null) {
throw new Error(
'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
);
}
const showWarning = name => {
if (__DEV__) {
console.error(
'%s is not available on event objects created from event responder modules (React Flare). ' +
'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.%s }`',
name,
name,
);
}
};
eventValue.isDefaultPrevented = () => {
if (__DEV__) {
showWarning('isDefaultPrevented()');
}
};
eventValue.isPropagationStopped = () => {
if (__DEV__) {
showWarning('isPropagationStopped()');
}
};
// $FlowFixMe: we don't need value, Flow thinks we do
Object.defineProperty(eventValue, 'nativeEvent', {
get() {
if (__DEV__) {
showWarning('nativeEvent');
}
},
});
}
}
function doesFiberHaveResponder(
fiber: Fiber,
responder: ReactDOMEventResponder,
): boolean {
const tag = fiber.tag;
if (tag === HostComponent || tag === ScopeComponent) {
const dependencies = fiber.dependencies;
if (dependencies !== null) {
const respondersMap = dependencies.responders;
if (respondersMap !== null && respondersMap.has(responder)) {
return true;
}
}
}
return false;
}
function getActiveDocument(): Document {
return ((currentDocument: any): Document);
}
function createDOMResponderEvent(
domEventName: string,
nativeEvent: AnyNativeEvent,
nativeEventTarget: Element | Document,
passive: boolean,
): ReactDOMResponderEvent {
const {buttons, pointerType} = (nativeEvent: any);
let eventPointerType = '';
if (pointerType !== undefined) {
eventPointerType = pointerType;
} else if (nativeEvent.key !== undefined) {
eventPointerType = 'keyboard';
} else if (buttons !== undefined) {
eventPointerType = 'mouse';
} else if ((nativeEvent: any).changedTouches !== undefined) {
eventPointerType = 'touch';
}
return {
nativeEvent: nativeEvent,
passive,
pointerType: eventPointerType,
target: nativeEventTarget,
type: domEventName,
};
}
function responderEventTypesContainType(
eventTypes: Array<string>,
type: string,
isPassive: boolean,
): boolean {
for (let i = 0, len = eventTypes.length; i < len; i++) {
const eventType = eventTypes[i];
if (eventType === type || (!isPassive && eventType === type + '_active')) {
return true;
}
}
return false;
}
function validateResponderTargetEventTypes(
eventType: string,
responder: ReactDOMEventResponder,
isPassive: boolean,
): boolean {
const {targetEventTypes} = responder;
// Validate the target event type exists on the responder
if (targetEventTypes !== null) {
return responderEventTypesContainType(
targetEventTypes,
eventType,
isPassive,
);
}
return false;
}
function traverseAndHandleEventResponderInstances(
domEventName: string,
targetFiber: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: Document | Element,
eventSystemFlags: EventSystemFlags,
): void {
const isPassiveEvent = (eventSystemFlags & IS_PASSIVE) !== 0;
const isPassiveSupported = (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0;
const isPassive = isPassiveEvent || !isPassiveSupported;
// Trigger event responders in this order:
// - Bubble target responder phase
// - Root responder phase
const visitedResponders = new Set();
const responderEvent = createDOMResponderEvent(
domEventName,
nativeEvent,
nativeEventTarget,
isPassiveEvent,
);
let node = targetFiber;
let insidePortal = false;
while (node !== null) {
const tag = node.tag;
const dependencies = node.dependencies;
if (tag === HostPortal) {
insidePortal = true;
} else if (
(tag === HostComponent || tag === ScopeComponent) &&
dependencies !== null
) {
const respondersMap = dependencies.responders;
if (respondersMap !== null) {
const responderInstances = Array.from(respondersMap.values());
for (let i = 0, length = responderInstances.length; i < length; i++) {
const responderInstance = responderInstances[i];
const {props, responder, state} = responderInstance;
if (
!visitedResponders.has(responder) &&
validateResponderTargetEventTypes(
domEventName,
responder,
isPassive,
) &&
(!insidePortal || responder.targetPortalPropagation)
) {
visitedResponders.add(responder);
const onEvent = responder.onEvent;
if (onEvent !== null) {
currentInstance = responderInstance;
onEvent(responderEvent, eventResponderContext, props, state);
if (currentPropagationBehavior === PropagateToNextResponder) {
visitedResponders.delete(responder);
currentPropagationBehavior = DoNotPropagateToNextResponder;
}
}
}
}
}
}
node = node.return;
}
// Root phase
const passive = rootEventTypesToEventResponderInstances.get(domEventName);
const rootEventResponderInstances = [];
if (passive !== undefined) {
rootEventResponderInstances.push(...Array.from(passive));
}
if (!isPassive) {
const active = rootEventTypesToEventResponderInstances.get(
domEventName + '_active',
);
if (active !== undefined) {
rootEventResponderInstances.push(...Array.from(active));
}
}
if (rootEventResponderInstances.length > 0) {
const responderInstances = Array.from(rootEventResponderInstances);
for (let i = 0; i < responderInstances.length; i++) {
const responderInstance = responderInstances[i];
const {props, responder, state} = responderInstance;
const onRootEvent = responder.onRootEvent;
if (onRootEvent !== null) {
currentInstance = responderInstance;
onRootEvent(responderEvent, eventResponderContext, props, state);
}
}
}
}
export function mountEventResponder(
responder: ReactDOMEventResponder,
responderInstance: ReactDOMEventResponderInstance,
props: Object,
state: Object,
) {
const onMount = responder.onMount;
if (onMount !== null) {
const previousInstance = currentInstance;
currentInstance = responderInstance;
try {
onMount(eventResponderContext, props, state);
} finally {
currentInstance = previousInstance;
}
}
}
export function unmountEventResponder(
responderInstance: ReactDOMEventResponderInstance,
): void {
const responder = ((responderInstance.responder: any): ReactDOMEventResponder);
const onUnmount = responder.onUnmount;
if (onUnmount !== null) {
const {props, state} = responderInstance;
const previousInstance = currentInstance;
currentInstance = responderInstance;
try {
onUnmount(eventResponderContext, props, state);
} finally {
currentInstance = previousInstance;
}
}
const rootEventTypesSet = responderInstance.rootEventTypes;
if (rootEventTypesSet !== null) {
const rootEventTypes = Array.from(rootEventTypesSet);
for (let i = 0; i < rootEventTypes.length; i++) {
const topLevelEventType = rootEventTypes[i];
const rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
topLevelEventType,
);
if (rootEventResponderInstances !== undefined) {
rootEventResponderInstances.delete(responderInstance);
}
}
}
}
function validateResponderContext(): void {
invariant(
currentInstance !== null,
'An event responder context was used outside of an event cycle.',
);
}
export function DEPRECATED_dispatchEventForResponderEventSystem(
domEventName: string,
targetFiber: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: Document | Element,
eventSystemFlags: EventSystemFlags,
): void {
if (enableDeprecatedFlareAPI) {
const previousInstance = currentInstance;
const previousTimeStamp = currentTimeStamp;
const previousDocument = currentDocument;
const previousPropagationBehavior = currentPropagationBehavior;
currentPropagationBehavior = DoNotPropagateToNextResponder;
// nodeType 9 is DOCUMENT_NODE
currentDocument =
(nativeEventTarget: any).nodeType === 9
? ((nativeEventTarget: any): Document)
: (nativeEventTarget: any).ownerDocument;
// We might want to control timeStamp another way here
currentTimeStamp = (nativeEvent: any).timeStamp;
try {
batchedEventUpdates(() => {
traverseAndHandleEventResponderInstances(
domEventName,
targetFiber,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
);
});
} finally {
currentInstance = previousInstance;
currentTimeStamp = previousTimeStamp;
currentDocument = previousDocument;
currentPropagationBehavior = previousPropagationBehavior;
}
}
}
export function addRootEventTypesForResponderInstance(
responderInstance: ReactDOMEventResponderInstance,
rootEventTypes: Array<string>,
): void {
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
DEPRECATED_registerRootEventType(rootEventType, responderInstance);
}
}
function DEPRECATED_registerRootEventType(
rootEventType: string,
eventResponderInstance: ReactDOMEventResponderInstance,
): void {
let rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
rootEventType,
);
if (rootEventResponderInstances === undefined) {
rootEventResponderInstances = new Set();
rootEventTypesToEventResponderInstances.set(
rootEventType,
rootEventResponderInstances,
);
}
let rootEventTypesSet = eventResponderInstance.rootEventTypes;
if (rootEventTypesSet === null) {
rootEventTypesSet = eventResponderInstance.rootEventTypes = new Set();
}
invariant(
!rootEventTypesSet.has(rootEventType),
'addRootEventTypes() found a duplicate root event ' +
'type of "%s". This might be because the event type exists in the event responder "rootEventTypes" ' +
'array or because of a previous addRootEventTypes() using this root event type.',
rootEventType,
);
rootEventTypesSet.add(rootEventType);
rootEventResponderInstances.add(eventResponderInstance);
}
export function addResponderEventSystemEvent(
document: Document,
domEventName: string,
passive: boolean,
): any => void {
let eventFlags = RESPONDER_EVENT_SYSTEM;
// If passive option is not supported, then the event will be
// active and not passive, but we flag it as using not being
// supported too. This way the responder event plugins know,
// and can provide polyfills if needed.
if (passive) {
if (passiveBrowserEventsSupported) {
eventFlags |= IS_PASSIVE;
} else {
eventFlags |= PASSIVE_NOT_SUPPORTED;
passive = false;
}
}
// Check if interactive and wrap in discreteUpdates
const listener = createEventListenerWrapper(
document,
((domEventName: any): DOMEventName),
eventFlags,
);
if (passiveBrowserEventsSupported) {
return addEventCaptureListenerWithPassiveFlag(
document,
domEventName,
listener,
passive,
);
} else {
return addEventCaptureListener(document, domEventName, listener);
}
}
export function removeTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
capture: boolean,
listener: any => void,
): void {
removeEventListener(targetContainer, domEventName, listener, capture);
}

View File

@ -9,16 +9,12 @@
export type EventSystemFlags = number;
export const PLUGIN_EVENT_SYSTEM = 1;
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
export const IS_EVENT_HANDLE_NON_MANAGED_NODE = 1 << 2;
export const IS_NON_DELEGATED = 1 << 3;
export const IS_CAPTURE_PHASE = 1 << 4;
export const IS_PASSIVE = 1 << 5;
export const IS_REPLAYED = 1 << 6;
export const IS_LEGACY_FB_SUPPORT_MODE = 1 << 7;
// This is used by React Flare
export const PASSIVE_NOT_SUPPORTED = 1 << 8;
export const IS_EVENT_HANDLE_NON_MANAGED_NODE = 1;
export const IS_NON_DELEGATED = 1 << 1;
export const IS_CAPTURE_PHASE = 1 << 2;
export const IS_PASSIVE = 1 << 3;
export const IS_REPLAYED = 1 << 4;
export const IS_LEGACY_FB_SUPPORT_MODE = 1 << 5;
export const SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE =
IS_LEGACY_FB_SUPPORT_MODE | IS_REPLAYED | IS_CAPTURE_PHASE;

View File

@ -17,7 +17,6 @@ import type {DOMEventName} from '../events/DOMEventNames';
// CommonJS interop named imports.
import * as Scheduler from 'scheduler';
import {DEPRECATED_dispatchEventForResponderEventSystem} from './DeprecatedDOMEventResponderSystem';
import {
isReplayableDiscreteEvent,
queueDiscreteEvent,
@ -34,17 +33,12 @@ import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
import {
type EventSystemFlags,
IS_LEGACY_FB_SUPPORT_MODE,
PLUGIN_EVENT_SYSTEM,
RESPONDER_EVENT_SYSTEM,
} from './EventSystemFlags';
import getEventTarget from './getEventTarget';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
import {
enableDeprecatedFlareAPI,
enableLegacyFBSupport,
} from 'shared/ReactFeatureFlags';
import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
import {
UserBlockingEvent,
ContinuousEvent,
@ -136,7 +130,7 @@ function dispatchDiscreteEvent(
// flushed for this event and we don't need to do it again.
(eventSystemFlags & IS_LEGACY_FB_SUPPORT_MODE) === 0
) {
flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
flushDiscreteUpdatesIfNeeded();
}
discreteUpdates(
dispatchEvent,
@ -238,35 +232,13 @@ export function dispatchEvent(
// This is not replayable so we'll invoke it but without a target,
// in case the event system needs to trace it.
if (enableDeprecatedFlareAPI) {
if (eventSystemFlags & PLUGIN_EVENT_SYSTEM) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
if (eventSystemFlags & RESPONDER_EVENT_SYSTEM) {
// React Flare event system
DEPRECATED_dispatchEventForResponderEventSystem(
(domEventName: any),
null,
nativeEvent,
getEventTarget(nativeEvent),
eventSystemFlags,
);
}
} else {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
// Attempt dispatching an event. Returns a SuspenseInstance or Container if it's blocked.
@ -318,36 +290,13 @@ export function attemptToDispatchEvent(
}
}
}
if (enableDeprecatedFlareAPI) {
if (eventSystemFlags & PLUGIN_EVENT_SYSTEM) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
}
if (eventSystemFlags & RESPONDER_EVENT_SYSTEM) {
// React Flare event system
DEPRECATED_dispatchEventForResponderEventSystem(
(domEventName: any),
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
);
}
} else {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
// We're not blocked on anything.
return null;
}

View File

@ -10,15 +10,11 @@
import type {AnyNativeEvent} from '../events/PluginModuleType';
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
import type {DOMEventName} from '../events/DOMEventNames';
import type {ElementListenerMap} from '../client/ReactDOMComponentTree';
import type {EventSystemFlags} from './EventSystemFlags';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {LanePriority} from 'react-reconciler/src/ReactFiberLane';
import {
enableDeprecatedFlareAPI,
enableSelectiveHydration,
} from 'shared/ReactFeatureFlags';
import {enableSelectiveHydration} from 'shared/ReactFeatureFlags';
import {
unstable_runWithPriority as runWithPriority,
unstable_scheduleCallback as scheduleCallback,
@ -34,7 +30,6 @@ import {attemptToDispatchEvent} from './ReactDOMEventListener';
import {
getInstanceFromNode,
getClosestInstanceFromNode,
getEventListenerMap,
} from '../client/ReactDOMComponentTree';
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
@ -88,7 +83,6 @@ type PointerEvent = Event & {
import {IS_REPLAYED} from './EventSystemFlags';
import {listenToNativeEvent} from './DOMPluginEventSystem';
import {addResponderEventSystemEvent} from './DeprecatedDOMEventResponderSystem';
type QueuedReplayableEvent = {|
blockedOn: null | Container | SuspenseInstance,
@ -186,43 +180,17 @@ function trapReplayableEventForContainer(
listenToNativeEvent(domEventName, false, ((container: any): Element), null);
}
function trapReplayableEventForDocument(
domEventName: DOMEventName,
document: Document,
listenerMap: ElementListenerMap,
) {
if (enableDeprecatedFlareAPI) {
// Trap events for the responder system.
// TODO: Ideally we shouldn't need these to be active but
// if we only have a passive listener, we at least need it
// to still pretend to be active so that Flare gets those
// events.
const activeEventKey = domEventName + '_active';
if (!listenerMap.has(activeEventKey)) {
const listener = addResponderEventSystemEvent(
document,
domEventName,
false,
);
listenerMap.set(activeEventKey, {passive: false, listener});
}
}
}
export function eagerlyTrapReplayableEvents(
container: Container,
document: Document,
) {
const listenerMapForDoc = getEventListenerMap(document);
// Discrete
discreteReplayableEvents.forEach(domEventName => {
trapReplayableEventForContainer(domEventName, container);
trapReplayableEventForDocument(domEventName, document, listenerMapForDoc);
});
// Continuous
continuousReplayableEvents.forEach(domEventName => {
trapReplayableEventForContainer(domEventName, container);
trapReplayableEventForDocument(domEventName, document, listenerMapForDoc);
});
}

View File

@ -10,9 +10,6 @@ import {
restoreStateIfNeeded,
} from './ReactDOMControlledComponent';
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
// Used as a way to call batchedUpdates when we don't have a reference to
// the renderer. Such as when we're dispatching events or if third party
// libraries need to call batchedUpdates. Eventually, this API will go away when
@ -77,18 +74,6 @@ export function batchedEventUpdates(fn, a, b) {
}
}
// This is for the React Flare event system
export function executeUserEventHandler(fn: any => void, value: any): void {
const previouslyInEventHandler = isInsideEventHandler;
try {
isInsideEventHandler = true;
const type = typeof value === 'object' && value !== null ? value.type : '';
invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
} finally {
isInsideEventHandler = previouslyInEventHandler;
}
}
export function discreteUpdates(fn, a, b, c, d) {
const prevIsInsideEventHandler = isInsideEventHandler;
isInsideEventHandler = true;
@ -102,27 +87,8 @@ export function discreteUpdates(fn, a, b, c, d) {
}
}
let lastFlushedEventTimeStamp = 0;
export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
// event.timeStamp isn't overly reliable due to inconsistencies in
// how different browsers have historically provided the time stamp.
// Some browsers provide high-resolution time stamps for all events,
// some provide low-resolution time stamps for all events. FF < 52
// even mixes both time stamps together. Some browsers even report
// negative time stamps or time stamps that are 0 (iOS9) in some cases.
// Given we are only comparing two time stamps with equality (!==),
// we are safe from the resolution differences. If the time stamp is 0
// we bail-out of preventing the flush, which can affect semantics,
// such as if an earlier flush removes or adds event listeners that
// are fired in the subsequent flush. However, this is the same
// behaviour as we had before this change, so the risks are low.
if (
!isInsideEventHandler &&
(!enableDeprecatedFlareAPI ||
timeStamp === 0 ||
lastFlushedEventTimeStamp !== timeStamp)
) {
lastFlushedEventTimeStamp = timeStamp;
export function flushDiscreteUpdatesIfNeeded() {
if (!isInsideEventHandler) {
flushDiscreteUpdatesImpl();
}
}

View File

@ -8,13 +8,13 @@
*/
import {canUseDOM} from 'shared/ExecutionEnvironment';
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
export let passiveBrowserEventsSupported = false;
// Check if browser support events with passive listeners
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
if (enableDeprecatedFlareAPI && canUseDOM) {
if (enableCreateEventHandleAPI && canUseDOM) {
try {
const options = {};
// $FlowFixMe: Ignore Flow complaining about needing a value

View File

@ -23,7 +23,6 @@ import {
disableModulePatternComponents,
enableSuspenseServerRenderer,
enableFundamentalAPI,
enableDeprecatedFlareAPI,
enableScopeAPI,
} from 'shared/ReactFeatureFlags';
@ -368,9 +367,6 @@ function createOpenTagMarkup(
if (!hasOwnProperty.call(props, propKey)) {
continue;
}
if (enableDeprecatedFlareAPI && propKey === 'DEPRECATED_flareListeners') {
continue;
}
let propValue = props[propKey];
if (propValue == null) {
continue;

View File

@ -14,7 +14,6 @@ import type {
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import type {SuspenseConfig} from 'react-reconciler/src/ReactFiberSuspenseConfig';
import type PartialRenderer from './ReactPartialRenderer';
@ -457,13 +456,6 @@ export function useCallback<T>(
return useMemo(() => callback, deps);
}
function useResponder(responder, props): ReactEventResponderListener<any, any> {
return {
props,
responder,
};
}
// TODO Decide on how to implement this hook for server rendering.
// If a mutation occurs during render, consider triggering a Suspense boundary
// and falling back to client rendering.
@ -521,7 +513,6 @@ export const Dispatcher: DispatcherType = {
useEffect: noop,
// Debugging effect
useDebugValue: noop,
useResponder,
useDeferredValue,
useTransition,
useOpaqueIdentifier,

View File

@ -7,10 +7,7 @@
* @flow
*/
import {
enableDeprecatedFlareAPI,
enableFilterEmptyStringAttributesDOM,
} from 'shared/ReactFeatureFlags';
import {enableFilterEmptyStringAttributesDOM} from 'shared/ReactFeatureFlags';
type PropertyType = 0 | 1 | 2 | 3 | 4 | 5 | 6;
@ -252,9 +249,6 @@ const reservedProps = [
'suppressHydrationWarning',
'style',
];
if (enableDeprecatedFlareAPI) {
reservedProps.push('DEPRECATED_flareListeners');
}
reservedProps.forEach(name => {
properties[name] = new PropertyInfoRecord(

View File

@ -5,8 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
const hasReadOnlyValue = {
button: true,
checkbox: true,
@ -29,8 +27,7 @@ export function checkControlledValueProps(
props.onInput ||
props.readOnly ||
props.disabled ||
props.value == null ||
(enableDeprecatedFlareAPI && props.DEPRECATED_flareListeners)
props.value == null
)
) {
console.error(
@ -46,8 +43,7 @@ export function checkControlledValueProps(
props.onChange ||
props.readOnly ||
props.disabled ||
props.checked == null ||
(enableDeprecatedFlareAPI && props.DEPRECATED_flareListeners)
props.checked == null
)
) {
console.error(

View File

@ -9,75 +9,15 @@
import type {
ReactFundamentalComponentInstance,
ReactEventResponder,
ReactEventResponderInstance,
EventPriority,
ReactScopeInstance,
} from 'shared/ReactTypes';
import type {DOMEventName} from '../events/DOMEventNames';
type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
export type PointerType =
| ''
| 'mouse'
| 'keyboard'
| 'pen'
| 'touch'
| 'trackpad';
export type ReactDOMResponderEvent = {
nativeEvent: AnyNativeEvent,
passive: boolean,
pointerType: PointerType,
target: Element | Document,
type: string,
...
};
export type ReactDOMEventResponder = ReactEventResponder<
ReactDOMResponderEvent,
ReactDOMResponderContext,
>;
export type ReactDOMEventResponderInstance = ReactEventResponderInstance<
ReactDOMResponderEvent,
ReactDOMResponderContext,
>;
export type ReactDOMFundamentalComponentInstance = ReactFundamentalComponentInstance<
any,
any,
>;
export type ReactDOMResponderContext = {
dispatchEvent: (
eventValue: any,
listener: (any) => void,
eventPriority: EventPriority,
) => void,
isTargetWithinNode: (
childTarget: Element | Document,
parentTarget: Element | Document,
) => boolean,
isTargetWithinResponder: (null | Element | Document) => boolean,
isTargetWithinResponderScope: (null | Element | Document) => boolean,
addRootEventTypes: (rootEventTypes: Array<string>) => void,
removeRootEventTypes: (rootEventTypes: Array<string>) => void,
getActiveDocument(): Document,
objectAssign: Function,
getTimeStamp: () => number,
isTargetWithinHostComponent: (
target: Element | Document,
elementType: string,
) => boolean,
continuePropagation(): void,
// Used for controller components
enqueueStateRestore(Element | Document): void,
getResponderNode(): Element | null,
...
};
export type ReactDOMEventHandle = (
target: EventTarget | ReactScopeInstance,
callback: (SyntheticEvent<EventTarget>) => void,

View File

@ -1,105 +0,0 @@
# `react-interactions/events`
*This package is experimental. It is intended for use with the experimental React
events API that is not available in open source builds.*
Event Responders attach to a host node. They listen to native browser events
dispatched on the host node of their child and transform those events into
high-level events for applications.
The core API is documented below. Documentation for individual Event Responders
can be found [here](./docs).
## Event Responder Interface
Note: React Responders require the internal React flag `enableDeprecatedFlareAPI`.
An Event Responder Interface is defined using an object. Each responder can define DOM
events to listen to, handle the synthetic responder events, dispatch custom
events, and implement a state machine.
```js
// types
type ResponderEventType = string;
type ResponderEvent = {|
nativeEvent: any,
target: Element | Document,
pointerType: string,
type: string,
passive: boolean,
|};
type CustomEvent = {
type: string,
target: Element,
...
}
```
### getInitialState?: (props: null | Object) => Object
The initial state of that the Event Responder is created with.
### onEvent?: (event: ResponderEvent, context: ResponderContext, props, state)
Called during the bubble phase of the `targetEventTypes` dispatched on DOM
elements within the Event Responder.
### onMount?: (context: ResponderContext, props, state)
Called after an Event Responder in mounted.
### onRootEvent?: (event: ResponderEvent, context: ResponderContext, props, state)
Called when any of the `rootEventTypes` are dispatched on the root of the app.
### onUnmount?: (context: ResponderContext, props, state)
Called before an Event Responder in unmounted.
### rootEventTypes?: Array<ResponderEventType>
Defines the DOM events to listen to on the root of the app.
### targetEventTypes?: Array<ResponderEventType>
Defines the DOM events to listen to within the Event Responder subtree.
## ResponderContext
The Event Responder Context is exposed via the `context` argument for certain methods
on the `EventResponder` object.
### addRootEventTypes(eventTypes: Array<ResponderEventType>)
This can be used to dynamically listen to events on the root of the app only
when it is necessary to do so.
### dispatchEvent(propName: string, event: CustomEvent, { discrete: boolean })
Dispatches a custom synthetic event. The `type` and `target` are required
fields if the event is an object, but any other fields can be defined on the `event` that will be passed
to the `listener`. You can also pass a value that is not an object, but a `boolean`. For example:
```js
const event = { type: 'press', target, pointerType, x, y };
context.dispatchEvent('onPress', event, DiscreteEvent);
```
### isTargetWithinNode(target: Element, element: Element): boolean
Returns `true` if `target` is a child of `element`.
### isTargetWithinResponder(target: Element): boolean
Returns `true` is the target element is within the subtree of the Event Responder.
### isTargetWithinResponderScope(target: Element): boolean
Returns `true` is the target element is within the current Event Responder's scope. If the target element
is within the scope of the same responder, but owned by another Event Responder instance, this will return `false`.
### removeRootEventTypes(eventTypes: Array<ResponderEventType>)
Remove the root event types added with `addRootEventTypes`.

View File

@ -1,10 +0,0 @@
/**
* 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
*/
export * from './src/dom/ContextMenu';

View File

@ -1,10 +0,0 @@
/**
* 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
*/
export * from './src/dom/DeprecatedFocus';

View File

@ -1,54 +0,0 @@
# ContextMenu
The `useContextMenu` hooks responds to context-menu events.
```js
// Example
const Button = (props) => {
const contextmenu = useContextMenu({
disabled,
onContextMenu,
preventDefault
});
return (
<div DEPRECATED_flareListeners={contextmenu}>
{props.children}
</div>
);
};
```
## Types
```js
type ContextMenuEvent = {
altKey: boolean,
buttons: 0 | 1 | 2,
ctrlKey: boolean,
metaKey: boolean,
pageX: number,
pageY: number,
pointerType: PointerType,
shiftKey: boolean,
target: Element,
timeStamp: number,
type: 'contextmenu',
x: number,
y: number,
}
```
## Props
### disabled: boolean = false
Disables the responder.
### onContextMenu: (e: ContextMenuEvent) => void
Called when the user performs a gesture to display a context menu.
### preventDefault: boolean = true
Prevents the native behavior (i.e., context menu).

View File

@ -1,64 +0,0 @@
# Focus
The `useFocus` hook responds to focus and blur events on its child. Focus events
are dispatched for all input types, with the exception of `onFocusVisibleChange`
which is only dispatched when focusing with a keyboard.
Focus events do not propagate between `useFocus` event responders.
```js
// Example
const Button = (props) => {
const [ isFocusVisible, setFocusVisible ] = useState(false);
const focus = useFocus({
onBlur={props.onBlur}
onFocus={props.onFocus}
onFocusVisibleChange={setFocusVisible}
});
return (
<button
children={props.children}
DEPRECATED_flareListeners={focus}
style={{
...(isFocusVisible && focusVisibleStyles)
}}
>
);
};
```
## Types
```js
type FocusEvent = {
target: Element,
pointerType: 'mouse' | 'touch' | 'pen' | 'keyboard',
timeStamp: number,
type: 'blur' | 'focus' | 'focuschange' | 'focusvisiblechange'
}
```
## Props
### disabled: boolean = false
Disables the responder.
### onBlur: (e: FocusEvent) => void
Called when the element loses focus.
### onFocus: (e: FocusEvent) => void
Called when the element gains focus.
### onFocusChange: boolean => void
Called when the element changes focus state (i.e., after `onBlur` and
`onFocus`).
### onFocusVisibleChange: boolean => void
Called when the element receives or loses focus following keyboard navigation.
This can be used to display focus styles only for keyboard interactions.

View File

@ -1,48 +0,0 @@
# FocusWithin
The `useFocusWithin` hooks responds to focus and blur events on its child. Focus events
are dispatched for all input types, with the exception of `onFocusVisibleChange`
which is only dispatched when focusing with a keyboard.
Focus events do not propagate between `useFocusWithin` event responders.
```js
// Example
const Button = (props) => {
const [ isFocusWithin, updateFocusWithin ] = useState(false);
const [ isFocusWithinVisible, updateFocusWithinVisible ] = useState(false);
const focusWithin = useFocusWithin({
onFocusWithinChange={updateFocusWithin}
onFocusWithinVisibleChange={updateFocusWithinVisible}
});
return (
<button
children={props.children}
DEPRECATED_flareListeners={focusWithin}
style={{
...(isFocusWithin && focusWithinStyles),
...(isFocusWithinVisible && focusWithinVisibleStyles)
}}
>
);
};
```
## Props
### disabled: boolean = false
Disables the responder.
### onFocusWithinChange: boolean => void
Called once the element or a descendant receives focus, and once focus moves
outside of the element.
### onFocusWithinVisibleChange: boolean => void
Called once the element or a descendant is focused following keyboard
navigation, and once focus moves outside of the element. This can be used to
display focus styles only when the keyboard is being used to focus within the
element's subtree.

View File

@ -1,75 +0,0 @@
# Hover
The `useHover` hook responds to hover events on the element it wraps. Hover
events are only dispatched for `mouse` and `pen` pointer types. Hover begins
when the pointer enters the element's bounds and ends when the pointer leaves.
Hover events do not propagate between `useHover` event responders.
```js
// Example
const Link = (props) => (
const [ isHovered, setHovered ] = useState(false);
const hover = useHover({
onHoverChange: setHovered
});
return (
<a
{...props}
href={props.href}
DEPRECATED_flareListeners={hover}
style={{
...props.style,
textDecoration: isHovered ? 'underline': 'none'
}}
/>
);
);
```
## Types
```js
type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
type HoverEvent = {|
clientX: number,
clientY: number,
pageX: number,
pageY: number,
pointerType: PointerType,
target: Element,
timeStamp: number,
type: HoverEventType,
x: number,
y: number,
|};
```
## Props
### disabled: boolean
Disables the responder.
### onHoverChange: boolean => void
Called when the element changes hover state (i.e., after `onHoverStart` and
`onHoverEnd`).
### onHoverEnd: (e: HoverEvent) => void
Called once the element is no longer hovered.
### onHoverMove: (e: HoverEvent) => void
Called when the pointer moves within the hit bounds of the element.
### onHoverStart: (e: HoverEvent) => void
Called once the element is hovered.
### preventDefault: boolean = true
Whether to `preventDefault()` native events.

View File

@ -1,121 +0,0 @@
# Press
The `usePress` hook responds to press events on the element it wraps. Press
events are dispatched for `mouse`, `pen`, `touch`, `trackpad`, and `keyboard`
pointer types. Press events are only dispatched for keyboards when pressing the
Enter or Spacebar keys. If `onPress` is not called, this signifies that the
press ended outside of the element hit bounds (i.e., the user aborted the
press).
Press events do not propagate between `usePress` event responders.
```js
// Example
const Button = (props) => (
const [ isPressed, setPressed ] = useState(false);
const press = usePress({
onPress={props.onPress}
onPressChange={setPressed}
});
return (
<div
{...props}
DEPRECATED_flareListeners={press}
role="button"
tabIndex={0}
style={
...buttonStyles,
...(isPressed && pressedStyles)
}}
/>
);
);
```
## Types
```js
type PressEvent = {
altKey: boolean,
buttons: 0 | 1 | 4,
ctrlKey: boolean,
defaultPrevented: boolean,
metaKey: boolean,
pageX: number,
pageY: number,
pointerType:
| 'mouse'
| 'touch'
| 'pen'
| 'trackpad'
| 'keyboard',
screenX: number,
screenY: number,
shiftKey: boolean,
target: Element,
timeStamp: number,
type:
| 'press'
| 'pressstart'
| 'pressend'
| 'presschange'
| 'pressmove'
| 'contextmenu',
x: number,
y: number
}
type PressOffset = {
top?: number,
right?: number,
bottom?: number,
right?: number
};
```
## Props
### disabled: boolean = false
Disables the responder.
### onPress: (e: PressEvent) => void
Called immediately after a press is released, unless the press is released
outside the hit bounds of the element (accounting for `pressRetentionOffset`.
### onPressChange: boolean => void
Called when the element changes press state (i.e., after `onPressStart` and
`onPressEnd`).
### onPressEnd: (e: PressEvent) => void
Called once the element is no longer pressed (because the press was released,
cancelled, or moved beyond the hit bounds).
### onPressMove: (e: PressEvent) => void
Called when a press moves within the hit bounds of the element. Never called for
keyboard-initiated press events.
### onPressStart: (e: PressEvent) => void
Called once the element is pressed down.
### pressRetentionOffset: PressOffset
Defines how far the pointer (while held down) may move outside the bounds of the
element before it is deactivated. Once deactivated, the pointer (still held
down) can be moved back within the bounds of the element to reactivate it.
Ensure you pass in a constant to reduce memory allocations. Default is `20` for
each offset.
### preventDefault: boolean = true
Whether to `preventDefault()` native events. Native behavior is prevented by
default. If an anchor is the child of `Press`, internal and external navigation
should be performed in `onPress`. To rely on native behavior instead, set
`preventDefault` to `false`, but be aware that native behavior will take place
immediately after interaction.

View File

@ -1,10 +0,0 @@
/**
* 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
*/
export * from './src/dom/Hover';

View File

@ -1,10 +0,0 @@
/**
* 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
*/
export * from './src/dom/Input';

View File

@ -1,10 +0,0 @@
/**
* 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
*/
export * from './src/dom/PressLegacy';

View File

@ -1,126 +0,0 @@
/**
* 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,
PointerType,
} from 'react-dom/src/shared/ReactDOMTypes';
import type {ReactEventResponderListener} from 'shared/ReactTypes';
import * as React from 'react';
import {DiscreteEvent} from 'shared/ReactTypes';
type ContextMenuProps = {|
disabled: boolean,
onContextMenu: (e: ContextMenuEvent) => void,
preventDefault: boolean,
|};
type ContextMenuState = {pointerType: PointerType, ...};
type ContextMenuEvent = {|
altKey: boolean,
buttons: 0 | 1 | 2,
ctrlKey: boolean,
metaKey: boolean,
pageX: null | number,
pageY: null | number,
pointerType: PointerType,
shiftKey: boolean,
target: Element | Document,
timeStamp: number,
type: 'contextmenu',
x: null | number,
y: null | number,
|};
const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent != null;
function dispatchContextMenuEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: ContextMenuProps,
state: ContextMenuState,
): void {
const nativeEvent: any = event.nativeEvent;
const target = event.target;
const timeStamp = context.getTimeStamp();
const pointerType = state.pointerType;
const gestureState = {
altKey: nativeEvent.altKey,
buttons: nativeEvent.buttons != null ? nativeEvent.buttons : 0,
ctrlKey: nativeEvent.ctrlKey,
metaKey: nativeEvent.metaKey,
pageX: nativeEvent.pageX,
pageY: nativeEvent.pageY,
pointerType,
shiftKey: nativeEvent.shiftKey,
target,
timeStamp,
type: 'contextmenu',
x: nativeEvent.clientX,
y: nativeEvent.clientY,
};
context.dispatchEvent(gestureState, props.onContextMenu, DiscreteEvent);
}
const contextMenuImpl = {
targetEventTypes: hasPointerEvents
? ['contextmenu_active', 'pointerdown']
: ['contextmenu_active', 'touchstart', 'mousedown'],
getInitialState(): ContextMenuState {
return {
pointerType: '',
};
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: ContextMenuProps,
state: ContextMenuState,
): void {
const nativeEvent: any = event.nativeEvent;
const pointerType = event.pointerType;
const type = event.type;
if (props.disabled) {
return;
}
if (type === 'contextmenu') {
const onContextMenu = props.onContextMenu;
const preventDefault = props.preventDefault;
if (preventDefault !== false && !nativeEvent.defaultPrevented) {
nativeEvent.preventDefault();
}
if (typeof onContextMenu === 'function') {
dispatchContextMenuEvent(event, context, props, state);
}
state.pointerType = '';
} else {
state.pointerType = pointerType;
}
},
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const ContextMenuResponder = React.DEPRECATED_createResponder(
'ContextMenu',
contextMenuImpl,
);
export function useContextMenu(
props: ContextMenuProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(ContextMenuResponder, props);
}

View File

@ -1,718 +0,0 @@
/**
* 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,
PointerType,
} from 'react-dom/src/shared/ReactDOMTypes';
import type {ReactEventResponderListener} from 'shared/ReactTypes';
import * as React from 'react';
import {DiscreteEvent} from 'shared/ReactTypes';
/**
* Types
*/
type FocusEvent = {|
relatedTarget: null | Element | Document,
target: Element | Document,
type: FocusEventType | FocusWithinEventType,
pointerType: PointerType,
timeStamp: number,
continuePropagation: () => void,
|};
type FocusState = {
detachedTarget: null | Element | Document,
focusTarget: null | Element | Document,
isFocused: boolean,
isFocusVisible: boolean,
pointerType: PointerType,
addedRootEvents?: boolean,
};
type FocusProps = {
disabled: boolean,
onBlur: (e: FocusEvent) => void,
onFocus: (e: FocusEvent) => void,
onFocusChange: boolean => void,
onFocusVisibleChange: boolean => void,
...
};
type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
type FocusWithinProps = {
disabled?: boolean,
onFocusWithin?: (e: FocusEvent) => void,
onAfterBlurWithin?: (e: FocusEvent) => void,
onBeforeBlurWithin?: (e: FocusEvent) => void,
onBlurWithin?: (e: FocusEvent) => void,
onFocusWithinChange?: boolean => void,
onFocusWithinVisibleChange?: boolean => void,
...
};
type FocusWithinEventType =
| 'focuswithinvisiblechange'
| 'focuswithinchange'
| 'blurwithin'
| 'focuswithin'
| 'beforeblurwithin'
| 'afterblurwithin';
/**
* Shared between Focus and FocusWithin
*/
let isGlobalFocusVisible = true;
let hasTrackedGlobalFocusVisible = false;
let globalFocusVisiblePointerType = '';
let isEmulatingMouseEvents = false;
const isMac =
typeof window !== 'undefined' && window.navigator != null
? /^Mac/.test(window.navigator.platform)
: false;
let passiveBrowserEventsSupported = false;
const canUseDOM: boolean = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
);
// Check if browser support events with passive listeners
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
if (canUseDOM) {
try {
const options = {};
// $FlowFixMe: Ignore Flow complaining about needing a value
Object.defineProperty(options, 'passive', {
get: function() {
passiveBrowserEventsSupported = true;
},
});
window.addEventListener('test', options, options);
window.removeEventListener('test', options, options);
} catch (e) {
passiveBrowserEventsSupported = false;
}
}
const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent != null;
const focusVisibleEvents = hasPointerEvents
? ['keydown', 'keyup', 'pointermove', 'pointerdown', 'pointerup']
: ['keydown', 'keyup', 'mousedown', 'touchmove', 'touchstart', 'touchend'];
const targetEventTypes = ['focus', 'blur', 'beforeblur', ...focusVisibleEvents];
const rootEventTypes = ['afterblur'];
function addWindowEventListener(types, callback, options) {
types.forEach(type => {
window.addEventListener(type, callback, options);
});
}
function trackGlobalFocusVisible() {
if (!hasTrackedGlobalFocusVisible) {
hasTrackedGlobalFocusVisible = true;
addWindowEventListener(
focusVisibleEvents,
handleGlobalFocusVisibleEvent,
passiveBrowserEventsSupported ? {capture: true, passive: true} : true,
);
}
}
function handleGlobalFocusVisibleEvent(
nativeEvent: MouseEvent | TouchEvent | KeyboardEvent,
): void {
const {type} = nativeEvent;
switch (type) {
case 'pointermove':
case 'pointerdown':
case 'pointerup': {
isGlobalFocusVisible = false;
globalFocusVisiblePointerType = (nativeEvent: any).pointerType;
break;
}
case 'keydown':
case 'keyup': {
const {metaKey, altKey, ctrlKey} = nativeEvent;
const validKey = !(metaKey || (!isMac && altKey) || ctrlKey);
if (validKey) {
globalFocusVisiblePointerType = 'keyboard';
isGlobalFocusVisible = true;
}
break;
}
// fallbacks for no PointerEvent support
case 'touchmove':
case 'touchstart':
case 'touchend': {
isEmulatingMouseEvents = true;
isGlobalFocusVisible = false;
globalFocusVisiblePointerType = 'touch';
break;
}
case 'mousedown': {
if (!isEmulatingMouseEvents) {
isGlobalFocusVisible = false;
globalFocusVisiblePointerType = 'mouse';
} else {
isEmulatingMouseEvents = false;
}
break;
}
}
}
function isFunction(obj): boolean {
return typeof obj === 'function';
}
function createFocusEvent(
context: ReactDOMResponderContext,
type: FocusEventType | FocusWithinEventType,
target: Element | Document,
pointerType: PointerType,
relatedTarget: null | Element | Document,
): FocusEvent {
return {
relatedTarget,
target,
type,
pointerType,
timeStamp: context.getTimeStamp(),
// 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();
},
};
}
function handleFocusVisibleTargetEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
state: FocusState,
callback: boolean => void,
): void {
const {type} = event;
isGlobalFocusVisible = false;
// Focus should stop being visible if a pointer is used on the element
// after it was focused using a keyboard.
const focusTarget = state.focusTarget;
if (
focusTarget !== null &&
(type === 'mousedown' || type === 'touchstart' || type === 'pointerdown')
) {
callback(false);
}
}
function handleFocusVisibleTargetEvents(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
state: FocusState,
callback: boolean => void,
): void {
const {type} = event;
state.pointerType = globalFocusVisiblePointerType;
switch (type) {
case 'pointermove':
case 'pointerdown':
case 'pointerup': {
handleFocusVisibleTargetEvent(event, context, state, callback);
break;
}
case 'keydown':
case 'keyup': {
const nativeEvent = event.nativeEvent;
const focusTarget = state.focusTarget;
const {metaKey, altKey, ctrlKey} = (nativeEvent: any);
const validKey = !(metaKey || (!isMac && altKey) || ctrlKey);
if (validKey) {
if (focusTarget !== null) {
callback(true);
}
}
break;
}
// fallbacks for no PointerEvent support
case 'touchmove':
case 'touchstart':
case 'touchend': {
handleFocusVisibleTargetEvent(event, context, state, callback);
break;
}
case 'mousedown': {
if (!isEmulatingMouseEvents) {
handleFocusVisibleTargetEvent(event, context, state, callback);
}
break;
}
}
}
/**
* Focus Responder
*/
function dispatchFocusEvents(
context: ReactDOMResponderContext,
props: FocusProps,
state: FocusState,
) {
const pointerType = state.pointerType;
const target = ((state.focusTarget: any): Element | Document);
const onFocus = props.onFocus;
if (isFunction(onFocus)) {
const syntheticEvent = createFocusEvent(
context,
'focus',
target,
pointerType,
null,
);
context.dispatchEvent(syntheticEvent, onFocus, DiscreteEvent);
}
dispatchFocusChange(context, props, true);
if (state.isFocusVisible) {
dispatchFocusVisibleChangeEvent(context, props, true);
}
}
function dispatchBlurEvents(
context: ReactDOMResponderContext,
props: FocusProps,
state: FocusState,
) {
const pointerType = state.pointerType;
const target = ((state.focusTarget: any): Element | Document);
const onBlur = props.onBlur;
if (isFunction(onBlur)) {
const syntheticEvent = createFocusEvent(
context,
'blur',
target,
pointerType,
null,
);
context.dispatchEvent(syntheticEvent, onBlur, DiscreteEvent);
}
dispatchFocusChange(context, props, false);
if (state.isFocusVisible) {
dispatchFocusVisibleChangeEvent(context, props, false);
}
}
function dispatchFocusWithinEvents(
context: ReactDOMResponderContext,
event: ReactDOMResponderEvent,
props: FocusWithinProps,
state: FocusState,
) {
const pointerType = state.pointerType;
const target = ((state.focusTarget: any): Element | Document) || event.target;
const onFocusWithin = (props.onFocusWithin: any);
if (isFunction(onFocusWithin)) {
const syntheticEvent = createFocusEvent(
context,
'focuswithin',
target,
pointerType,
null,
);
context.dispatchEvent(syntheticEvent, onFocusWithin, DiscreteEvent);
}
}
function dispatchBlurWithinEvents(
context: ReactDOMResponderContext,
event: ReactDOMResponderEvent,
props: FocusWithinProps,
state: FocusState,
) {
const pointerType = state.pointerType;
const target = ((state.focusTarget: any): Element | Document) || event.target;
const onBlurWithin = (props.onBlurWithin: any);
if (isFunction(onBlurWithin)) {
const syntheticEvent = createFocusEvent(
context,
'blurwithin',
target,
pointerType,
null,
);
context.dispatchEvent(syntheticEvent, onBlurWithin, DiscreteEvent);
}
}
function dispatchAfterBlurWithinEvents(
context: ReactDOMResponderContext,
event: ReactDOMResponderEvent,
props: FocusWithinProps,
state: FocusState,
) {
const pointerType = state.pointerType;
const onAfterBlurWithin = (props.onAfterBlurWithin: any);
const relatedTarget = state.detachedTarget;
if (isFunction(onAfterBlurWithin) && relatedTarget !== null) {
const syntheticEvent = createFocusEvent(
context,
'afterblurwithin',
relatedTarget,
pointerType,
relatedTarget,
);
context.dispatchEvent(syntheticEvent, onAfterBlurWithin, DiscreteEvent);
}
}
function dispatchFocusChange(
context: ReactDOMResponderContext,
props: FocusProps,
value: boolean,
): void {
const onFocusChange = props.onFocusChange;
if (isFunction(onFocusChange)) {
context.dispatchEvent(value, onFocusChange, DiscreteEvent);
}
}
function dispatchFocusVisibleChangeEvent(
context: ReactDOMResponderContext,
props: FocusProps,
value: boolean,
) {
const onFocusVisibleChange = props.onFocusVisibleChange;
if (isFunction(onFocusVisibleChange)) {
context.dispatchEvent(value, onFocusVisibleChange, DiscreteEvent);
}
}
function unmountFocusResponder(
context: ReactDOMResponderContext,
props: FocusProps,
state: FocusState,
) {
if (state.isFocused) {
dispatchBlurEvents(context, props, state);
}
}
const focusResponderImpl = {
targetEventTypes,
targetPortalPropagation: true,
getInitialState(): FocusState {
return {
detachedTarget: null,
focusTarget: null,
isFocused: false,
isFocusVisible: false,
pointerType: '',
addedRootEvents: false,
};
},
onMount() {
trackGlobalFocusVisible();
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: FocusProps,
state: FocusState,
): void {
const {type, target} = event;
if (props.disabled) {
if (state.isFocused) {
dispatchBlurEvents(context, props, state);
state.isFocused = false;
state.focusTarget = null;
}
return;
}
switch (type) {
case 'focus': {
state.focusTarget = context.getResponderNode();
// Limit focus events to the direct child of the event component.
// Browser focus is not expected to bubble.
if (!state.isFocused && state.focusTarget === target) {
state.isFocused = true;
state.isFocusVisible = isGlobalFocusVisible;
dispatchFocusEvents(context, props, state);
}
isEmulatingMouseEvents = false;
break;
}
case 'blur': {
if (state.isFocused) {
dispatchBlurEvents(context, props, state);
state.isFocusVisible = isGlobalFocusVisible;
state.isFocused = false;
}
// This covers situations where focus is lost to another document in
// the same window (e.g., iframes). Any action that restores focus to
// the document (e.g., touch or click) first causes 'focus' to be
// dispatched, which means the 'pointerType' we provide is stale
// (it reflects the *previous* pointer). We cannot determine the
// 'pointerType' in this case, so a blur with no
// relatedTarget is used as a signal to reset the 'pointerType'.
if (event.nativeEvent.relatedTarget == null) {
state.pointerType = '';
}
isEmulatingMouseEvents = false;
break;
}
default:
handleFocusVisibleTargetEvents(
event,
context,
state,
isFocusVisible => {
if (state.isFocused && state.isFocusVisible !== isFocusVisible) {
state.isFocusVisible = isFocusVisible;
dispatchFocusVisibleChangeEvent(context, props, isFocusVisible);
}
},
);
}
},
onUnmount(
context: ReactDOMResponderContext,
props: FocusProps,
state: FocusState,
) {
unmountFocusResponder(context, props, state);
},
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const FocusResponder = React.DEPRECATED_createResponder(
'Focus',
focusResponderImpl,
);
export function useFocus(
props: FocusProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(FocusResponder, props);
}
/**
* FocusWithin Responder
*/
function dispatchFocusWithinChangeEvent(
context: ReactDOMResponderContext,
props: FocusWithinProps,
state: FocusState,
value: boolean,
) {
const onFocusWithinChange = (props.onFocusWithinChange: any);
if (isFunction(onFocusWithinChange)) {
context.dispatchEvent(value, onFocusWithinChange, DiscreteEvent);
}
if (state.isFocusVisible) {
dispatchFocusWithinVisibleChangeEvent(context, props, state, value);
}
}
function dispatchFocusWithinVisibleChangeEvent(
context: ReactDOMResponderContext,
props: FocusWithinProps,
state: FocusState,
value: boolean,
) {
const onFocusWithinVisibleChange = (props.onFocusWithinVisibleChange: any);
if (isFunction(onFocusWithinVisibleChange)) {
context.dispatchEvent(value, onFocusWithinVisibleChange, DiscreteEvent);
}
}
function unmountFocusWithinResponder(
context: ReactDOMResponderContext,
props: FocusWithinProps,
state: FocusState,
) {
if (state.isFocused) {
dispatchFocusWithinChangeEvent(context, props, state, false);
}
}
const focusWithinResponderImpl = {
targetEventTypes,
targetPortalPropagation: true,
getInitialState(): FocusState {
return {
detachedTarget: null,
focusTarget: null,
isFocused: false,
isFocusVisible: false,
pointerType: '',
};
},
onMount() {
trackGlobalFocusVisible();
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: FocusWithinProps,
state: FocusState,
): void {
const {nativeEvent, type} = event;
const relatedTarget = (nativeEvent: any).relatedTarget;
if (props.disabled) {
if (state.isFocused) {
dispatchFocusWithinChangeEvent(context, props, state, false);
state.isFocused = false;
state.focusTarget = null;
}
return;
}
switch (type) {
case 'focus': {
state.focusTarget = context.getResponderNode();
// Limit focus events to the direct child of the event component.
// Browser focus is not expected to bubble.
if (!state.isFocused) {
state.isFocused = true;
state.isFocusVisible = isGlobalFocusVisible;
dispatchFocusWithinChangeEvent(context, props, state, true);
}
if (!state.isFocusVisible && isGlobalFocusVisible) {
state.isFocusVisible = isGlobalFocusVisible;
dispatchFocusWithinVisibleChangeEvent(context, props, state, true);
}
dispatchFocusWithinEvents(context, event, props, state);
break;
}
case 'blur': {
if (
state.isFocused &&
!context.isTargetWithinResponder(relatedTarget)
) {
dispatchFocusWithinChangeEvent(context, props, state, false);
dispatchBlurWithinEvents(context, event, props, state);
state.isFocused = false;
}
break;
}
case 'beforeblur': {
const onBeforeBlurWithin = (props.onBeforeBlurWithin: any);
if (isFunction(onBeforeBlurWithin)) {
const syntheticEvent = createFocusEvent(
context,
'beforeblurwithin',
event.target,
state.pointerType,
null,
);
state.detachedTarget = event.target;
context.dispatchEvent(
syntheticEvent,
onBeforeBlurWithin,
DiscreteEvent,
);
if (!state.addedRootEvents) {
state.addedRootEvents = true;
context.addRootEventTypes(rootEventTypes);
}
} else {
// We want to propagate to next focusWithin responder
// if this responder doesn't handle beforeblur
context.continuePropagation();
}
break;
}
default:
handleFocusVisibleTargetEvents(
event,
context,
state,
isFocusVisible => {
if (state.isFocused && state.isFocusVisible !== isFocusVisible) {
state.isFocusVisible = isFocusVisible;
dispatchFocusWithinVisibleChangeEvent(
context,
props,
state,
isFocusVisible,
);
}
},
);
}
},
onRootEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: FocusWithinProps,
state: FocusState,
): void {
if (event.type === 'afterblur') {
const detachedTarget = state.detachedTarget;
if (
detachedTarget !== null &&
detachedTarget === event.nativeEvent.relatedTarget
) {
dispatchAfterBlurWithinEvents(context, event, props, state);
state.detachedTarget = null;
if (state.addedRootEvents) {
state.addedRootEvents = false;
context.removeRootEventTypes(rootEventTypes);
}
}
}
},
onUnmount(
context: ReactDOMResponderContext,
props: FocusWithinProps,
state: FocusState,
) {
unmountFocusWithinResponder(context, props, state);
},
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const FocusWithinResponder = React.DEPRECATED_createResponder(
'FocusWithin',
focusWithinResponderImpl,
);
export function useFocusWithin(
props: FocusWithinProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(FocusWithinResponder, props);
}

View File

@ -1,391 +0,0 @@
/**
* 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,
PointerType,
} from 'react-dom/src/shared/ReactDOMTypes';
import type {ReactEventResponderListener} from 'shared/ReactTypes';
import * as React from 'react';
import {UserBlockingEvent} from 'shared/ReactTypes';
type HoverProps = {
disabled: boolean,
preventDefault: boolean,
onHoverChange: boolean => void,
onHoverEnd: (e: HoverEvent) => void,
onHoverMove: (e: HoverEvent) => void,
onHoverStart: (e: HoverEvent) => void,
...
};
type HoverState = {
hoverTarget: null | Element | Document,
isActiveHovered: boolean,
isHovered: boolean,
isTouched?: boolean,
ignoreEmulatedMouseEvents?: boolean,
...
};
type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
type HoverEvent = {|
clientX: null | number,
clientY: null | number,
pageX: null | number,
pageY: null | number,
pointerType: PointerType,
screenX: null | number,
screenY: null | number,
target: Element | Document,
timeStamp: number,
type: HoverEventType,
x: null | number,
y: null | number,
|};
const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent != null;
function isFunction(obj): boolean {
return typeof obj === 'function';
}
function createHoverEvent(
event: ?ReactDOMResponderEvent,
context: ReactDOMResponderContext,
type: HoverEventType,
target: Element | Document,
): HoverEvent {
let clientX = null;
let clientY = null;
let pageX = null;
let pageY = null;
let screenX = null;
let screenY = null;
let pointerType = '';
if (event) {
const nativeEvent = (event.nativeEvent: any);
pointerType = event.pointerType;
({clientX, clientY, pageX, pageY, screenX, screenY} = nativeEvent);
}
return {
pointerType,
target,
type,
timeStamp: context.getTimeStamp(),
clientX,
clientY,
pageX,
pageY,
screenX,
screenY,
x: clientX,
y: clientY,
};
}
function dispatchHoverChangeEvent(
event: null | ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): void {
const onHoverChange = props.onHoverChange;
if (isFunction(onHoverChange)) {
const bool = state.isActiveHovered;
context.dispatchEvent(bool, onHoverChange, UserBlockingEvent);
}
}
function dispatchHoverStartEvents(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): boolean {
const target = state.hoverTarget;
if (event !== null) {
const {nativeEvent} = event;
if (
context.isTargetWithinResponderScope((nativeEvent: any).relatedTarget)
) {
return false;
}
}
state.isHovered = true;
if (!state.isActiveHovered) {
state.isActiveHovered = true;
const onHoverStart = props.onHoverStart;
if (isFunction(onHoverStart)) {
const syntheticEvent = createHoverEvent(
event,
context,
'hoverstart',
((target: any): Element | Document),
);
context.dispatchEvent(syntheticEvent, onHoverStart, UserBlockingEvent);
}
dispatchHoverChangeEvent(event, context, props, state);
}
return true;
}
function dispatchHoverMoveEvent(event, context, props, state) {
const target = state.hoverTarget;
const onHoverMove = props.onHoverMove;
if (isFunction(onHoverMove)) {
const syntheticEvent = createHoverEvent(
event,
context,
'hovermove',
((target: any): Element | Document),
);
context.dispatchEvent(syntheticEvent, onHoverMove, UserBlockingEvent);
}
}
function dispatchHoverEndEvents(
event: null | ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): boolean {
const target = state.hoverTarget;
if (event !== null) {
const {nativeEvent} = event;
if (
context.isTargetWithinResponderScope((nativeEvent: any).relatedTarget)
) {
return false;
}
}
state.isHovered = false;
if (state.isActiveHovered) {
state.isActiveHovered = false;
const onHoverEnd = props.onHoverEnd;
if (isFunction(onHoverEnd)) {
const syntheticEvent = createHoverEvent(
event,
context,
'hoverend',
((target: any): Element | Document),
);
context.dispatchEvent(syntheticEvent, onHoverEnd, UserBlockingEvent);
}
dispatchHoverChangeEvent(event, context, props, state);
state.hoverTarget = null;
state.isTouched = false;
}
return true;
}
function unmountResponder(
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): void {
if (state.isHovered) {
dispatchHoverEndEvents(null, context, props, state);
}
}
const rootPointerEventTypes = ['pointerout', 'pointermove', 'pointercancel'];
const hoverResponderImpl = {
targetEventTypes: ['pointerover'],
getInitialState() {
return {
isActiveHovered: false,
isHovered: false,
};
},
allowMultipleHostChildren: false,
allowEventHooks: true,
onRootEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): void {
const {type} = event;
switch (type) {
// MOVE
case 'pointermove': {
if (state.isHovered && state.hoverTarget !== null) {
dispatchHoverMoveEvent(event, context, props, state);
}
break;
}
// END
case 'pointercancel':
case 'pointerout': {
if (state.isHovered) {
if (
dispatchHoverEndEvents(event, context, props, state) ||
type === 'pointercancel'
) {
context.removeRootEventTypes(rootPointerEventTypes);
}
}
break;
}
}
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): void {
const {pointerType, type} = event;
if (props.disabled) {
if (state.isHovered) {
context.removeRootEventTypes(rootPointerEventTypes);
dispatchHoverEndEvents(event, context, props, state);
}
return;
}
switch (type) {
// START
case 'pointerover': {
if (!state.isHovered && pointerType !== 'touch') {
state.hoverTarget = context.getResponderNode();
if (dispatchHoverStartEvents(event, context, props, state)) {
context.addRootEventTypes(rootPointerEventTypes);
}
}
break;
}
}
},
onUnmount: unmountResponder,
};
const rootMouseEventTypes = ['mousemove', 'mouseout'];
const hoverResponderFallbackImpl = {
targetEventTypes: ['mouseover', 'mousemove', 'touchstart'],
getInitialState() {
return {
isActiveHovered: false,
isHovered: false,
isTouched: false,
ignoreEmulatedMouseEvents: false,
};
},
allowMultipleHostChildren: false,
allowEventHooks: true,
onRootEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): void {
const {type} = event;
switch (type) {
// MOVE
case 'mousemove': {
if (
state.isHovered &&
state.hoverTarget !== null &&
!state.ignoreEmulatedMouseEvents
) {
dispatchHoverMoveEvent(event, context, props, state);
}
break;
}
// END
case 'mouseout': {
if (state.isHovered) {
if (dispatchHoverEndEvents(event, context, props, state)) {
context.removeRootEventTypes(rootMouseEventTypes);
}
}
break;
}
}
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: HoverProps,
state: HoverState,
): void {
const {type} = event;
if (props.disabled) {
if (state.isHovered) {
context.removeRootEventTypes(rootMouseEventTypes);
dispatchHoverEndEvents(event, context, props, state);
state.ignoreEmulatedMouseEvents = false;
}
state.isTouched = false;
return;
}
switch (type) {
// START
case 'mouseover': {
if (!state.isHovered && !state.ignoreEmulatedMouseEvents) {
state.hoverTarget = context.getResponderNode();
if (dispatchHoverStartEvents(event, context, props, state)) {
context.addRootEventTypes(rootMouseEventTypes);
}
}
break;
}
// MOVE
case 'mousemove': {
if (!state.isHovered && type === 'mousemove') {
state.ignoreEmulatedMouseEvents = false;
state.isTouched = false;
}
break;
}
case 'touchstart': {
if (!state.isHovered) {
state.isTouched = true;
state.ignoreEmulatedMouseEvents = true;
}
break;
}
}
},
onUnmount: unmountResponder,
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const HoverResponder = React.DEPRECATED_createResponder(
'Hover',
hasPointerEvents ? hoverResponderImpl : hoverResponderFallbackImpl,
);
export function useHover(
props: HoverProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(HoverResponder, props);
}

View File

@ -1,225 +0,0 @@
/**
* 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);
}

View File

@ -1,924 +0,0 @@
/**
* 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,
PointerType,
} from 'react-dom/src/shared/ReactDOMTypes';
import type {
EventPriority,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import * as React from 'react';
import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
type PressProps = {|
disabled: boolean,
pressRetentionOffset: {
top: number,
right: number,
bottom: number,
left: number,
...
},
preventDefault: boolean,
onPress: (e: PressEvent) => void,
onPressChange: boolean => void,
onPressEnd: (e: PressEvent) => void,
onPressMove: (e: PressEvent) => void,
onPressStart: (e: PressEvent) => void,
|};
type PressState = {
activationPosition: null | $ReadOnly<{|
x: number,
y: number,
|}>,
addedRootEvents: boolean,
buttons: 0 | 1 | 4,
isActivePressed: boolean,
isActivePressStart: boolean,
isPressed: boolean,
isPressWithinResponderRegion: boolean,
pointerType: PointerType,
pressTarget: null | Element | Document,
responderRegionOnActivation: null | $ReadOnly<{|
bottom: number,
left: number,
right: number,
top: number,
|}>,
responderRegionOnDeactivation: null | $ReadOnly<{|
bottom: number,
left: number,
right: number,
top: number,
|}>,
ignoreEmulatedMouseEvents: boolean,
activePointerId: null | number,
shouldPreventClick: boolean,
touchEvent: null | Touch,
...
};
type PressEventType =
| 'press'
| 'pressmove'
| 'pressstart'
| 'pressend'
| 'presschange';
type PressEvent = {|
altKey: boolean,
buttons: 0 | 1 | 4,
clientX: null | number,
clientY: null | number,
ctrlKey: boolean,
defaultPrevented: boolean,
metaKey: boolean,
pageX: null | number,
pageY: null | number,
pointerType: PointerType,
screenX: null | number,
screenY: null | number,
shiftKey: boolean,
target: Element | Document,
timeStamp: number,
type: PressEventType,
x: null | number,
y: null | number,
preventDefault: () => void,
stopPropagation: () => void,
|};
const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent !== undefined;
const isMac =
typeof window !== 'undefined' && window.navigator != null
? /^Mac/.test(window.navigator.platform)
: false;
const DEFAULT_PRESS_RETENTION_OFFSET = {
bottom: 20,
top: 20,
left: 20,
right: 20,
};
const targetEventTypes = hasPointerEvents
? ['keydown_active', 'pointerdown_active', 'click_active']
: ['keydown_active', 'touchstart', 'mousedown_active', 'click_active'];
const rootEventTypes = hasPointerEvents
? [
'pointerup_active',
'pointermove',
'pointercancel',
'click',
'keyup',
'scroll',
'blur',
]
: [
'click',
'keyup',
'scroll',
'mousemove',
'touchmove',
'touchcancel',
// Used as a 'cancel' signal for mouse interactions
'dragstart',
'mouseup_active',
'touchend',
'blur',
];
function isFunction(obj): boolean {
return typeof obj === 'function';
}
function createPressEvent(
context: ReactDOMResponderContext,
type: PressEventType,
target: Element | Document,
pointerType: PointerType,
event: ?ReactDOMResponderEvent,
touchEvent: null | Touch,
defaultPrevented: boolean,
state: PressState,
): PressEvent {
const timeStamp = context.getTimeStamp();
let clientX = null;
let clientY = null;
let pageX = null;
let pageY = null;
let screenX = null;
let screenY = null;
let altKey = false;
let ctrlKey = false;
let metaKey = false;
let shiftKey = false;
let nativeEvent;
if (event) {
nativeEvent = (event.nativeEvent: any);
({altKey, ctrlKey, metaKey, shiftKey} = nativeEvent);
// Only check for one property, checking for all of them is costly. We can assume
// if clientX exists, so do the rest.
const eventObject = (touchEvent: any) || (nativeEvent: any);
if (eventObject) {
({clientX, clientY, pageX, pageY, screenX, screenY} = eventObject);
}
}
const pressEvent = {
altKey,
buttons: state.buttons,
clientX,
clientY,
ctrlKey,
defaultPrevented,
metaKey,
pageX,
pageY,
pointerType,
screenX,
screenY,
shiftKey,
target,
timeStamp,
type,
x: clientX,
y: clientY,
preventDefault() {
state.shouldPreventClick = true;
if (nativeEvent) {
pressEvent.defaultPrevented = true;
nativeEvent.preventDefault();
}
},
stopPropagation() {
// NO-OP, we should remove this in the future
if (__DEV__) {
console.error(
'stopPropagation is not available on event objects created from event responder modules (React Flare). ' +
'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.stopPropagation() }`',
);
}
},
};
return pressEvent;
}
function dispatchEvent(
event: ?ReactDOMResponderEvent,
listener: any => void,
context: ReactDOMResponderContext,
state: PressState,
name: PressEventType,
eventPriority: EventPriority,
): void {
const target = ((state.pressTarget: any): Element | Document);
const pointerType = state.pointerType;
const defaultPrevented =
(event != null && event.nativeEvent.defaultPrevented === true) ||
(name === 'press' && state.shouldPreventClick);
const touchEvent = state.touchEvent;
const syntheticEvent = createPressEvent(
context,
name,
target,
pointerType,
event,
touchEvent,
defaultPrevented,
state,
);
context.dispatchEvent(syntheticEvent, listener, eventPriority);
}
function dispatchPressChangeEvent(
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
const onPressChange = props.onPressChange;
if (isFunction(onPressChange)) {
const bool = state.isActivePressed;
context.dispatchEvent(bool, onPressChange, DiscreteEvent);
}
}
function dispatchPressStartEvents(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
state.isPressed = true;
if (!state.isActivePressStart) {
state.isActivePressStart = true;
const nativeEvent: any = event.nativeEvent;
const {clientX: x, clientY: y} = state.touchEvent || nativeEvent;
const wasActivePressed = state.isActivePressed;
state.isActivePressed = true;
if (x !== undefined && y !== undefined) {
state.activationPosition = {x, y};
}
const onPressStart = props.onPressStart;
if (isFunction(onPressStart)) {
dispatchEvent(
event,
onPressStart,
context,
state,
'pressstart',
DiscreteEvent,
);
}
if (!wasActivePressed) {
dispatchPressChangeEvent(context, props, state);
}
}
}
function dispatchPressEndEvents(
event: ?ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
state.isActivePressStart = false;
state.isPressed = false;
if (state.isActivePressed) {
state.isActivePressed = false;
const onPressEnd = props.onPressEnd;
if (isFunction(onPressEnd)) {
dispatchEvent(
event,
onPressEnd,
context,
state,
'pressend',
DiscreteEvent,
);
}
dispatchPressChangeEvent(context, props, state);
}
state.responderRegionOnDeactivation = null;
}
function dispatchCancel(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
state.touchEvent = null;
if (state.isPressed) {
state.ignoreEmulatedMouseEvents = false;
dispatchPressEndEvents(event, context, props, state);
}
removeRootEventTypes(context, state);
}
function isValidKeyboardEvent(nativeEvent: Object): boolean {
const {key, target} = nativeEvent;
const {tagName, isContentEditable} = target;
// Accessibility for keyboards. Space and Enter only.
// "Spacebar" is for IE 11
return (
(key === 'Enter' || key === ' ' || key === 'Spacebar') &&
tagName !== 'INPUT' &&
tagName !== 'TEXTAREA' &&
isContentEditable !== true
);
}
// TODO: account for touch hit slop
function calculateResponderRegion(
context: ReactDOMResponderContext,
target: Element,
props: PressProps,
) {
const pressRetentionOffset = context.objectAssign(
{},
DEFAULT_PRESS_RETENTION_OFFSET,
props.pressRetentionOffset,
);
let {left, right, bottom, top} = target.getBoundingClientRect();
if (pressRetentionOffset) {
if (pressRetentionOffset.bottom != null) {
bottom += pressRetentionOffset.bottom;
}
if (pressRetentionOffset.left != null) {
left -= pressRetentionOffset.left;
}
if (pressRetentionOffset.right != null) {
right += pressRetentionOffset.right;
}
if (pressRetentionOffset.top != null) {
top -= pressRetentionOffset.top;
}
}
return {
bottom,
top,
left,
right,
};
}
function getTouchFromPressEvent(nativeEvent: TouchEvent): null | Touch {
const targetTouches = nativeEvent.targetTouches;
if (targetTouches.length > 0) {
return targetTouches[0];
}
return null;
}
function unmountResponder(
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
if (state.isPressed) {
removeRootEventTypes(context, state);
dispatchPressEndEvents(null, context, props, state);
}
}
function addRootEventTypes(
context: ReactDOMResponderContext,
state: PressState,
): void {
if (!state.addedRootEvents) {
state.addedRootEvents = true;
context.addRootEventTypes(rootEventTypes);
}
}
function removeRootEventTypes(
context: ReactDOMResponderContext,
state: PressState,
): void {
if (state.addedRootEvents) {
state.addedRootEvents = false;
context.removeRootEventTypes(rootEventTypes);
}
}
function getTouchById(
nativeEvent: TouchEvent,
pointerId: null | number,
): null | Touch {
const changedTouches = nativeEvent.changedTouches;
for (let i = 0; i < changedTouches.length; i++) {
const touch = changedTouches[i];
if (touch.identifier === pointerId) {
return touch;
}
}
return null;
}
function getTouchTarget(context: ReactDOMResponderContext, touchEvent: Touch) {
const doc = context.getActiveDocument();
return doc.elementFromPoint(touchEvent.clientX, touchEvent.clientY);
}
function updateIsPressWithinResponderRegion(
nativeEventOrTouchEvent: Event | Touch,
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
// Calculate the responder region we use for deactivation if not
// already done during move event.
if (state.responderRegionOnDeactivation == null) {
state.responderRegionOnDeactivation = calculateResponderRegion(
context,
((state.pressTarget: any): Element),
props,
);
}
const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
let left, top, right, bottom;
if (responderRegionOnActivation != null) {
left = responderRegionOnActivation.left;
top = responderRegionOnActivation.top;
right = responderRegionOnActivation.right;
bottom = responderRegionOnActivation.bottom;
if (responderRegionOnDeactivation != null) {
left = Math.min(left, responderRegionOnDeactivation.left);
top = Math.min(top, responderRegionOnDeactivation.top);
right = Math.max(right, responderRegionOnDeactivation.right);
bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
}
}
const {clientX: x, clientY: y} = (nativeEventOrTouchEvent: any);
state.isPressWithinResponderRegion =
left != null &&
right != null &&
top != null &&
bottom != null &&
x !== null &&
y !== null &&
x >= left &&
x <= right &&
y >= top &&
y <= bottom;
}
// After some investigation work, screen reader virtual
// clicks (NVDA, Jaws, VoiceOver) do not have co-ords associated with the click
// event and "detail" is always 0 (where normal clicks are > 0)
function isScreenReaderVirtualClick(nativeEvent): boolean {
// JAWS/NVDA with Firefox.
if (nativeEvent.mozInputSource === 0 && nativeEvent.isTrusted) {
return true;
}
// Chrome
return (
nativeEvent.detail === 0 &&
nativeEvent.screenX === 0 &&
nativeEvent.screenY === 0 &&
nativeEvent.clientX === 0 &&
nativeEvent.clientY === 0
);
}
function targetIsDocument(target: null | Node): boolean {
// When target is null, it is the root
return target === null || target.nodeType === 9;
}
const pressResponderImpl = {
targetEventTypes,
getInitialState(): PressState {
return {
activationPosition: null,
addedRootEvents: false,
buttons: 0,
isActivePressed: false,
isActivePressStart: false,
isPressed: false,
isPressWithinResponderRegion: true,
pointerType: '',
pressTarget: null,
responderRegionOnActivation: null,
responderRegionOnDeactivation: null,
ignoreEmulatedMouseEvents: false,
activePointerId: null,
shouldPreventClick: false,
touchEvent: null,
};
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
const {pointerType, type} = event;
if (props.disabled) {
removeRootEventTypes(context, state);
dispatchPressEndEvents(event, context, props, state);
state.ignoreEmulatedMouseEvents = false;
return;
}
const nativeEvent: any = event.nativeEvent;
const isPressed = state.isPressed;
switch (type) {
// START
case 'pointerdown':
case 'keydown':
case 'mousedown':
case 'touchstart': {
if (!isPressed) {
const isTouchEvent = type === 'touchstart';
const isPointerEvent = type === 'pointerdown';
const isKeyboardEvent = pointerType === 'keyboard';
const isMouseEvent = pointerType === 'mouse';
// Ignore emulated mouse events
if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
return;
}
state.shouldPreventClick = false;
if (isTouchEvent) {
state.ignoreEmulatedMouseEvents = true;
} else if (isKeyboardEvent) {
// Ignore unrelated key events
if (isValidKeyboardEvent(nativeEvent)) {
const {
altKey,
ctrlKey,
metaKey,
shiftKey,
} = (nativeEvent: MouseEvent);
if (
props.preventDefault !== false &&
!shiftKey &&
!metaKey &&
!ctrlKey &&
!altKey
) {
// Prevent spacebar press from scrolling the window
const key = nativeEvent.key;
if (key === ' ' || key === 'Spacebar') {
nativeEvent.preventDefault();
}
state.shouldPreventClick = true;
}
} else {
return;
}
}
// We set these here, before the button check so we have this
// data around for handling of the context menu
state.pointerType = pointerType;
const pressTarget = (state.pressTarget = context.getResponderNode());
if (isPointerEvent) {
state.activePointerId = nativeEvent.pointerId;
} else if (isTouchEvent) {
const touchEvent = getTouchFromPressEvent(nativeEvent);
if (touchEvent === null) {
return;
}
state.touchEvent = touchEvent;
state.activePointerId = touchEvent.identifier;
}
// Ignore any device buttons except primary/middle and touch/pen contact.
// Additionally we ignore primary-button + ctrl-key with Macs as that
// acts like right-click and opens the contextmenu.
if (
nativeEvent.buttons === 2 ||
nativeEvent.buttons > 4 ||
(isMac && isMouseEvent && nativeEvent.ctrlKey)
) {
return;
}
// Exclude document targets
if (!targetIsDocument(pressTarget)) {
state.responderRegionOnActivation = calculateResponderRegion(
context,
((pressTarget: any): Element),
props,
);
}
state.responderRegionOnDeactivation = null;
state.isPressWithinResponderRegion = true;
state.buttons = nativeEvent.buttons;
if (nativeEvent.button === 1) {
state.buttons = 4;
}
dispatchPressStartEvents(event, context, props, state);
addRootEventTypes(context, state);
} else {
// Prevent spacebar press from scrolling the window
const key = nativeEvent.key;
if (
isValidKeyboardEvent(nativeEvent) &&
(key === ' ' || key === 'Spacebar')
) {
nativeEvent.preventDefault();
}
}
break;
}
case 'click': {
if (state.shouldPreventClick) {
nativeEvent.preventDefault();
}
const onPress = props.onPress;
if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
state.pointerType = 'keyboard';
state.pressTarget = context.getResponderNode();
const preventDefault = props.preventDefault;
if (preventDefault !== false) {
nativeEvent.preventDefault();
}
dispatchEvent(event, onPress, context, state, 'press', DiscreteEvent);
}
break;
}
}
},
onRootEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
): void {
const {pointerType, type} = event;
let target = event.target;
const nativeEvent: any = event.nativeEvent;
const isPressed = state.isPressed;
const activePointerId = state.activePointerId;
const previousPointerType = state.pointerType;
switch (type) {
// MOVE
case 'pointermove':
case 'mousemove':
case 'touchmove': {
let touchEvent;
// Ignore emulated events (pointermove will dispatch touch and mouse events)
// Ignore pointermove events during a keyboard press.
if (previousPointerType !== pointerType) {
return;
}
if (
type === 'pointermove' &&
activePointerId !== nativeEvent.pointerId
) {
return;
} else if (type === 'touchmove') {
touchEvent = getTouchById(nativeEvent, activePointerId);
if (touchEvent === null) {
return;
}
state.touchEvent = touchEvent;
}
const pressTarget = state.pressTarget;
if (pressTarget !== null && !targetIsDocument(pressTarget)) {
if (
pointerType === 'mouse' &&
context.isTargetWithinNode(target, pressTarget)
) {
state.isPressWithinResponderRegion = true;
} else {
// Calculate the responder region we use for deactivation, as the
// element dimensions may have changed since activation.
updateIsPressWithinResponderRegion(
touchEvent || nativeEvent,
context,
props,
state,
);
}
}
if (state.isPressWithinResponderRegion) {
if (isPressed) {
const onPressMove = props.onPressMove;
if (isFunction(onPressMove)) {
dispatchEvent(
event,
onPressMove,
context,
state,
'pressmove',
UserBlockingEvent,
);
}
} else {
dispatchPressStartEvents(event, context, props, state);
}
} else {
dispatchPressEndEvents(event, context, props, state);
}
break;
}
// END
case 'pointerup':
case 'keyup':
case 'mouseup':
case 'touchend': {
if (isPressed) {
const buttons = state.buttons;
let isKeyboardEvent = false;
let touchEvent;
if (
type === 'pointerup' &&
activePointerId !== nativeEvent.pointerId
) {
return;
} else if (type === 'touchend') {
touchEvent = getTouchById(nativeEvent, activePointerId);
if (touchEvent === null) {
return;
}
state.touchEvent = touchEvent;
target = getTouchTarget(context, touchEvent);
} else if (type === 'keyup') {
// Ignore unrelated keyboard events
if (!isValidKeyboardEvent(nativeEvent)) {
return;
}
isKeyboardEvent = true;
removeRootEventTypes(context, state);
} else if (buttons === 4) {
// Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
removeRootEventTypes(context, state);
}
// Determine whether to call preventDefault on subsequent native events.
if (
target !== null &&
context.isTargetWithinResponder(target) &&
context.isTargetWithinHostComponent(target, 'a')
) {
const {
altKey,
ctrlKey,
metaKey,
shiftKey,
} = (nativeEvent: MouseEvent);
// Check "open in new window/tab" and "open context menu" key modifiers
const preventDefault = props.preventDefault;
if (
preventDefault !== false &&
!shiftKey &&
!metaKey &&
!ctrlKey &&
!altKey
) {
state.shouldPreventClick = true;
}
}
const pressTarget = state.pressTarget;
dispatchPressEndEvents(event, context, props, state);
const onPress = props.onPress;
if (pressTarget !== null && isFunction(onPress)) {
if (
!isKeyboardEvent &&
pressTarget !== null &&
target !== null &&
!targetIsDocument(pressTarget)
) {
if (
pointerType === 'mouse' &&
context.isTargetWithinNode(target, pressTarget)
) {
state.isPressWithinResponderRegion = true;
} else {
// If the event target isn't within the press target, check if we're still
// within the responder region. The region may have changed if the
// element's layout was modified after activation.
updateIsPressWithinResponderRegion(
touchEvent || nativeEvent,
context,
props,
state,
);
}
}
if (state.isPressWithinResponderRegion && buttons !== 4) {
dispatchEvent(
event,
onPress,
context,
state,
'press',
DiscreteEvent,
);
}
}
state.touchEvent = null;
} else if (type === 'mouseup') {
state.ignoreEmulatedMouseEvents = false;
}
break;
}
case 'click': {
// "keyup" occurs after "click"
if (previousPointerType !== 'keyboard') {
removeRootEventTypes(context, state);
}
break;
}
// CANCEL
case 'scroll': {
// We ignore incoming scroll events when using mouse events
if (previousPointerType === 'mouse') {
return;
}
const pressTarget = state.pressTarget;
const scrollTarget = nativeEvent.target;
const doc = context.getActiveDocument();
// If the scroll target is the document or if the press target
// is inside the scroll target, then this a scroll that should
// trigger a cancel.
if (
pressTarget !== null &&
(scrollTarget === doc ||
context.isTargetWithinNode(pressTarget, scrollTarget))
) {
dispatchCancel(event, context, props, state);
}
break;
}
case 'pointercancel':
case 'touchcancel':
case 'dragstart': {
dispatchCancel(event, context, props, state);
break;
}
case 'blur': {
// If we encounter a blur that happens on the pressed target
// then disengage the blur.
if (isPressed && target === state.pressTarget) {
dispatchCancel(event, context, props, state);
}
}
}
},
onUnmount(
context: ReactDOMResponderContext,
props: PressProps,
state: PressState,
) {
unmountResponder(context, props, state);
},
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const PressResponder = React.DEPRECATED_createResponder(
'Press',
pressResponderImpl,
);
export function usePress(
props: PressProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(PressResponder, props);
}

View File

@ -1,734 +0,0 @@
/**
* 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 {
ReactDOMResponderContext,
ReactDOMResponderEvent,
PointerType,
} from 'react-dom/src/shared/ReactDOMTypes';
import type {ReactEventResponderListener} from 'shared/ReactTypes';
import * as React from 'react';
import {
buttonsEnum,
dispatchDiscreteEvent,
dispatchUserBlockingEvent,
getTouchById,
hasModifierKey,
hasPointerEvents,
} from './shared';
type TapProps = $ReadOnly<{|
disabled?: boolean,
maximumDistance?: number,
preventDefault?: boolean,
onAuxiliaryTap?: (e: TapEvent) => void,
onTapCancel?: (e: TapEvent) => void,
onTapChange?: boolean => void,
onTapEnd?: (e: TapEvent) => void,
onTapStart?: (e: TapEvent) => void,
onTapUpdate?: (e: TapEvent) => void,
|}>;
type TapGestureState = {|
altKey: boolean,
ctrlKey: boolean,
height: number,
metaKey: boolean,
pageX: number,
pageY: number,
pointerType: PointerType,
pressure: number,
screenX: number,
screenY: number,
shiftKey: boolean,
tangentialPressure: number,
target: null | Element,
tiltX: number,
tiltY: number,
timeStamp: number,
twist: number,
width: number,
x: number,
y: number,
|};
type TapState = {|
activePointerId: null | number,
buttons: 0 | 1 | 4,
gestureState: TapGestureState,
ignoreEmulatedEvents: boolean,
initialPosition: {|x: number, y: number|},
isActive: boolean,
isAuxiliaryActive: boolean,
pointerType: PointerType,
responderTarget: null | Element,
rootEvents: null | Array<string>,
shouldPreventDefault: boolean,
|};
type TapEventType =
| 'tap:auxiliary'
| 'tap:cancel'
| 'tap:change'
| 'tap:end'
| 'tap:start'
| 'tap:update';
type TapEvent = {|
...TapGestureState,
defaultPrevented: boolean,
type: TapEventType,
|};
/**
* Native event dependencies
*/
const targetEventTypes = hasPointerEvents
? ['pointerdown']
: ['mousedown', 'touchstart'];
const rootEventTypes = hasPointerEvents
? [
'click_active',
'contextmenu',
'pointerup',
'pointermove',
'pointercancel',
'scroll',
'blur',
]
: [
'click_active',
'contextmenu',
'mouseup',
'mousemove',
'dragstart',
'touchend',
'touchmove',
'touchcancel',
'scroll',
'blur',
];
/**
* Responder and gesture state
*/
function createInitialState(): TapState {
return {
activePointerId: null,
buttons: 0,
ignoreEmulatedEvents: false,
isActive: false,
isAuxiliaryActive: false,
initialPosition: {x: 0, y: 0},
pointerType: '',
responderTarget: null,
rootEvents: null,
shouldPreventDefault: true,
gestureState: {
altKey: false,
ctrlKey: false,
height: 1,
metaKey: false,
pageX: 0,
pageY: 0,
pointerType: '',
pressure: 0,
screenX: 0,
screenY: 0,
shiftKey: false,
tangentialPressure: 0,
target: null,
tiltX: 0,
tiltY: 0,
timeStamp: 0,
twist: 0,
width: 1,
x: 0,
y: 0,
},
};
}
function createPointerEventGestureState(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
event: ReactDOMResponderEvent,
): TapGestureState {
const timeStamp = context.getTimeStamp();
const nativeEvent = (event.nativeEvent: any);
const {
altKey,
ctrlKey,
height,
metaKey,
pageX,
pageY,
pointerType,
pressure,
screenX,
screenY,
shiftKey,
tangentialPressure,
tiltX,
tiltY,
twist,
width,
clientX,
clientY,
} = nativeEvent;
return {
altKey,
ctrlKey,
height,
metaKey,
pageX,
pageY,
pointerType,
pressure,
screenX,
screenY,
shiftKey,
tangentialPressure,
target: state.responderTarget,
tiltX,
tiltY,
timeStamp,
twist,
width,
x: clientX,
y: clientY,
};
}
function createFallbackGestureState(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
event: ReactDOMResponderEvent,
): TapGestureState {
const timeStamp = context.getTimeStamp();
const nativeEvent = (event.nativeEvent: any);
const eType = event.type;
const {altKey, ctrlKey, metaKey, shiftKey} = nativeEvent;
const isCancelType = eType === 'dragstart' || eType === 'touchcancel';
const isEndType = eType === 'mouseup' || eType === 'touchend';
const isTouchEvent = event.pointerType === 'touch';
let pointerEvent = nativeEvent;
if (!hasPointerEvents && isTouchEvent) {
const touch = getTouchById(nativeEvent, state.activePointerId);
if (touch != null) {
pointerEvent = touch;
}
}
const {
pageX,
pageY,
// $FlowExpectedError: missing from 'Touch' typedef
radiusX,
// $FlowExpectedError: missing from 'Touch' typedef
radiusY,
// $FlowExpectedError: missing from 'Touch' typedef
rotationAngle,
screenX,
screenY,
clientX,
clientY,
} = pointerEvent;
return {
altKey,
ctrlKey,
height: !isCancelType && radiusY != null ? radiusY * 2 : 1,
metaKey,
pageX: isCancelType ? 0 : pageX,
pageY: isCancelType ? 0 : pageY,
pointerType: event.pointerType,
pressure: isEndType || isCancelType ? 0 : isTouchEvent ? 1 : 0.5,
screenX: isCancelType ? 0 : screenX,
screenY: isCancelType ? 0 : screenY,
shiftKey,
tangentialPressure: 0,
target: state.responderTarget,
tiltX: 0,
tiltY: 0,
timeStamp,
twist: rotationAngle != null ? rotationAngle : 0,
width: !isCancelType && radiusX != null ? radiusX * 2 : 1,
x: isCancelType ? 0 : clientX,
y: isCancelType ? 0 : clientY,
};
}
const createGestureState = hasPointerEvents
? createPointerEventGestureState
: createFallbackGestureState;
/**
* Managing root events
*/
function addRootEventTypes(
rootEvents: Array<string>,
context: ReactDOMResponderContext,
state: TapState,
): void {
if (!state.rootEvents) {
state.rootEvents = rootEvents;
context.addRootEventTypes(state.rootEvents);
}
}
function removeRootEventTypes(
context: ReactDOMResponderContext,
state: TapState,
): void {
if (state.rootEvents != null) {
context.removeRootEventTypes(state.rootEvents);
state.rootEvents = null;
}
}
/**
* Managing pointers
*/
function getHitTarget(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
state: TapState,
): null | Element | Document {
if (!hasPointerEvents && event.pointerType === 'touch') {
const doc = context.getActiveDocument();
const nativeEvent: any = event.nativeEvent;
const touch = getTouchById(nativeEvent, state.activePointerId);
if (touch != null) {
return doc.elementFromPoint(touch.clientX, touch.clientY);
} else {
return null;
}
}
return event.target;
}
function isActivePointer(
event: ReactDOMResponderEvent,
state: TapState,
): boolean {
const nativeEvent: any = event.nativeEvent;
const activePointerId = state.activePointerId;
if (hasPointerEvents) {
const eventPointerId = nativeEvent.pointerId;
if (activePointerId != null && eventPointerId != null) {
return (
state.pointerType === event.pointerType &&
activePointerId === eventPointerId
);
} else {
return true;
}
} else {
if (event.pointerType === 'touch') {
const touch = getTouchById(nativeEvent, activePointerId);
return touch != null;
} else {
// accept all events that don't have pointer ids
return true;
}
}
}
function isAuxiliary(buttons: number, event: ReactDOMResponderEvent): boolean {
const nativeEvent: any = event.nativeEvent;
const isPrimaryPointer =
buttons === buttonsEnum.primary || event.pointerType === 'touch';
return (
// middle-click
buttons === buttonsEnum.auxiliary ||
// open-in-new-tab
(isPrimaryPointer && nativeEvent.metaKey) ||
// open-in-new-window
(isPrimaryPointer && nativeEvent.shiftKey)
);
}
function shouldActivate(event: ReactDOMResponderEvent): boolean {
const nativeEvent: any = event.nativeEvent;
const isPrimaryPointer =
nativeEvent.buttons === buttonsEnum.primary ||
event.pointerType === 'touch';
return isPrimaryPointer && !hasModifierKey(event);
}
/**
* Communicating gesture state back to components
*/
function dispatchStart(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
const type = 'tap:start';
const onTapStart = props.onTapStart;
if (onTapStart != null) {
const payload = context.objectAssign({}, state.gestureState, {type});
dispatchDiscreteEvent(context, payload, onTapStart);
}
dispatchChange(context, props, state);
}
function dispatchChange(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
const onTapChange = props.onTapChange;
if (onTapChange != null) {
const payload = state.isActive;
dispatchDiscreteEvent(context, payload, onTapChange);
}
}
function dispatchUpdate(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
) {
const type = 'tap:update';
const onTapUpdate = props.onTapUpdate;
if (onTapUpdate != null) {
const payload = context.objectAssign({}, state.gestureState, {type});
dispatchUserBlockingEvent(context, payload, onTapUpdate);
}
}
function dispatchEnd(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
const type = 'tap:end';
const onTapEnd = props.onTapEnd;
dispatchChange(context, props, state);
if (onTapEnd != null) {
const defaultPrevented = state.shouldPreventDefault === true;
const payload = context.objectAssign({}, state.gestureState, {
defaultPrevented,
type,
});
dispatchDiscreteEvent(context, payload, onTapEnd);
}
}
function dispatchCancel(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
const type = 'tap:cancel';
const onTapCancel = props.onTapCancel;
dispatchChange(context, props, state);
if (onTapCancel != null) {
const payload = context.objectAssign({}, state.gestureState, {type});
dispatchDiscreteEvent(context, payload, onTapCancel);
}
}
function dispatchAuxiliaryTap(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
const type = 'tap:auxiliary';
const onAuxiliaryTap = props.onAuxiliaryTap;
if (onAuxiliaryTap != null) {
const payload = context.objectAssign({}, state.gestureState, {
defaultPrevented: false,
type,
});
dispatchDiscreteEvent(context, payload, onAuxiliaryTap);
}
}
/**
* Responder implementation
*/
const responderImpl = {
targetEventTypes,
getInitialState(): TapState {
return createInitialState();
},
onEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
if (props.disabled) {
removeRootEventTypes(context, state);
if (state.isActive) {
state.isActive = false;
dispatchCancel(context, props, state);
}
return;
}
const nativeEvent: any = event.nativeEvent;
const eventTarget: Element = nativeEvent.target;
const eventType = event.type;
switch (eventType) {
// START
case 'pointerdown':
case 'mousedown':
case 'touchstart': {
if (eventType === 'mousedown' && state.ignoreEmulatedEvents) {
return;
}
if (!state.isActive) {
if (hasPointerEvents) {
const pointerId = nativeEvent.pointerId;
state.activePointerId = pointerId;
// Make mouse and touch pointers consistent.
// Flow bug: https://github.com/facebook/flow/issues/8055
// $FlowExpectedError
eventTarget.releasePointerCapture(pointerId);
} else {
if (eventType === 'touchstart') {
const targetTouches = nativeEvent.targetTouches;
if (targetTouches.length === 1) {
state.activePointerId = targetTouches[0].identifier;
} else {
return;
}
}
}
const activate = shouldActivate(event);
const buttons =
nativeEvent.button === 1
? buttonsEnum.auxiliary
: nativeEvent.buttons;
const activateAuxiliary = isAuxiliary(buttons, event);
if (activate || activateAuxiliary) {
state.buttons = buttons;
state.pointerType = event.pointerType;
state.responderTarget = context.getResponderNode();
addRootEventTypes(rootEventTypes, context, state);
if (!hasPointerEvents) {
if (eventType === 'touchstart') {
state.ignoreEmulatedEvents = true;
}
}
}
if (activateAuxiliary) {
state.isAuxiliaryActive = true;
} else if (activate) {
const gestureState = createGestureState(
context,
props,
state,
event,
);
state.isActive = true;
state.shouldPreventDefault = props.preventDefault !== false;
state.gestureState = gestureState;
state.initialPosition.x = gestureState.x;
state.initialPosition.y = gestureState.y;
dispatchStart(context, props, state);
}
} else if (
!isActivePointer(event, state) ||
(eventType === 'touchstart' && nativeEvent.targetTouches.length > 1)
) {
// Cancel the gesture if a second pointer becomes active on the target.
state.isActive = false;
dispatchCancel(context, props, state);
}
break;
}
}
},
onRootEvent(
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
const nativeEvent: any = event.nativeEvent;
const eventType = event.type;
const hitTarget = getHitTarget(event, context, state);
switch (eventType) {
// UPDATE
case 'pointermove':
case 'mousemove':
case 'touchmove': {
if (!hasPointerEvents) {
if (eventType === 'mousemove' && state.ignoreEmulatedEvents) {
return;
}
}
if (state.isActive && isActivePointer(event, state)) {
state.gestureState = createGestureState(context, props, state, event);
let shouldUpdate = true;
if (!context.isTargetWithinResponder(hitTarget)) {
shouldUpdate = false;
} else if (
props.maximumDistance != null &&
props.maximumDistance >= 10
) {
const maxDistance = props.maximumDistance;
const initialPosition = state.initialPosition;
const currentPosition = state.gestureState;
const moveX = initialPosition.x - currentPosition.x;
const moveY = initialPosition.y - currentPosition.y;
const moveDistance = Math.sqrt(moveX * moveX + moveY * moveY);
if (moveDistance > maxDistance) {
shouldUpdate = false;
}
}
if (shouldUpdate) {
dispatchUpdate(context, props, state);
} else {
state.isActive = false;
dispatchCancel(context, props, state);
}
}
break;
}
// END
case 'pointerup':
case 'mouseup':
case 'touchend': {
if (state.isActive && isActivePointer(event, state)) {
state.gestureState = createGestureState(context, props, state, event);
state.isActive = false;
if (isAuxiliary(state.buttons, event)) {
dispatchCancel(context, props, state);
dispatchAuxiliaryTap(context, props, state);
// Remove the root events here as no 'click' event is dispatched
removeRootEventTypes(context, state);
} else if (
!context.isTargetWithinResponder(hitTarget) ||
hasModifierKey(event)
) {
dispatchCancel(context, props, state);
} else {
dispatchEnd(context, props, state);
}
} else if (
state.isAuxiliaryActive &&
isAuxiliary(state.buttons, event)
) {
state.isAuxiliaryActive = false;
state.gestureState = createGestureState(context, props, state, event);
dispatchAuxiliaryTap(context, props, state);
// Remove the root events here as no 'click' event is dispatched
removeRootEventTypes(context, state);
}
if (!hasPointerEvents) {
if (eventType === 'mouseup') {
state.ignoreEmulatedEvents = false;
}
}
break;
}
// CANCEL
case 'contextmenu':
case 'pointercancel':
case 'touchcancel':
case 'dragstart': {
if (state.isActive && isActivePointer(event, state)) {
state.gestureState = createGestureState(context, props, state, event);
state.isActive = false;
dispatchCancel(context, props, state);
removeRootEventTypes(context, state);
}
break;
}
// CANCEL
case 'scroll': {
if (
state.isActive &&
state.responderTarget != null &&
// We ignore incoming scroll events when using mouse events
state.pointerType !== 'mouse' &&
// If the scroll target is the document or if the pointer target
// is within the 'scroll' target, then cancel the gesture
context.isTargetWithinNode(state.responderTarget, nativeEvent.target)
) {
state.gestureState = createGestureState(context, props, state, event);
state.isActive = false;
dispatchCancel(context, props, state);
removeRootEventTypes(context, state);
}
break;
}
case 'click': {
if (state.shouldPreventDefault) {
nativeEvent.preventDefault();
}
removeRootEventTypes(context, state);
break;
}
case 'blur': {
// If we encounter a blur that happens on the pressed target
// then disengage the blur.
if (state.isActive && nativeEvent.target === state.responderTarget) {
dispatchCancel(context, props, state);
}
}
}
},
onUnmount(
context: ReactDOMResponderContext,
props: TapProps,
state: TapState,
): void {
removeRootEventTypes(context, state);
if (state.isActive) {
state.isActive = false;
dispatchCancel(context, props, state);
}
},
};
// $FlowFixMe Can't add generic types without causing a parsing/syntax errors
export const TapResponder = React.DEPRECATED_createResponder(
'Tap',
responderImpl,
);
export function useTap(
props: TapProps,
): ?ReactEventResponderListener<any, any> {
return React.DEPRECATED_useResponder(TapResponder, props);
}

View File

@ -1,204 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
import {
buttonsType,
createEventTarget,
platform,
setPointerEvent,
} from 'dom-event-testing-library';
let React;
let ReactFeatureFlags;
let ReactDOM;
let useContextMenu;
function initializeModules(hasPointerEvents) {
setPointerEvent(hasPointerEvents);
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
useContextMenu = require('react-interactions/events/context-menu')
.useContextMenu;
}
}
const forcePointerEvents = true;
const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('ContextMenu responder', hasPointerEvents => {
let container;
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
ReactDOM.render(null, container);
document.body.removeChild(container);
container = null;
});
describe('all platforms', () => {
// @gate experimental
it('mouse right-click', () => {
const onContextMenu = jest.fn();
const preventDefault = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useContextMenu({onContextMenu});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.contextmenu({preventDefault});
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledWith(
expect.objectContaining({
buttons: buttonsType.secondary,
pointerType: 'mouse',
type: 'contextmenu',
}),
);
});
// @gate experimental
it('touch long-press', () => {
const onContextMenu = jest.fn();
const preventDefault = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useContextMenu({onContextMenu});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.contextmenu({preventDefault}, {pointerType: 'touch'});
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledWith(
expect.objectContaining({
buttons: buttonsType.none,
pointerType: 'touch',
type: 'contextmenu',
}),
);
});
// @gate experimental
it('"disabled" is true', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useContextMenu({
onContextMenu,
disabled: true,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.contextmenu();
expect(onContextMenu).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('"preventDefault" is false', () => {
const preventDefault = jest.fn();
const onContextMenu = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useContextMenu({
onContextMenu,
preventDefault: false,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.contextmenu({preventDefault});
expect(preventDefault).toHaveBeenCalledTimes(0);
expect(onContextMenu).toHaveBeenCalledTimes(1);
});
});
describe('mac platform', () => {
beforeEach(() => {
platform.set('mac');
jest.resetModules();
});
afterEach(() => {
platform.clear();
});
// @gate experimental
it('mouse modified left-click', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useContextMenu({onContextMenu});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.contextmenu({}, {modified: true});
expect(onContextMenu).toHaveBeenCalledTimes(1);
expect(onContextMenu).toHaveBeenCalledWith(
expect.objectContaining({
buttons: buttonsType.primary,
pointerType: 'mouse',
type: 'contextmenu',
}),
);
});
});
describe('windows platform', () => {
beforeEach(() => {
platform.set('windows');
jest.resetModules();
});
afterEach(() => {
platform.clear();
});
// @gate experimental
it('mouse modified left-click', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useContextMenu({onContextMenu});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.contextmenu({}, {modified: true});
expect(onContextMenu).toHaveBeenCalledTimes(0);
});
});
});

View File

@ -1,403 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
import {
createEventTarget,
setPointerEvent,
platform,
} from 'dom-event-testing-library';
let React;
let ReactFeatureFlags;
let ReactDOM;
let FocusResponder;
let useFocus;
function initializeModules(hasPointerEvents) {
setPointerEvent(hasPointerEvents);
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
FocusResponder = require('react-interactions/events/deprecated-focus')
.FocusResponder;
useFocus = require('react-interactions/events/deprecated-focus').useFocus;
}
}
const forcePointerEvents = true;
const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('Focus responder', hasPointerEvents => {
let container;
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
ReactDOM.render(null, container);
document.body.removeChild(container);
container = null;
});
describe('disabled', () => {
let onBlur, onFocus, ref;
const componentInit = () => {
onBlur = jest.fn();
onFocus = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useFocus({
disabled: true,
onBlur,
onFocus,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
target.blur();
expect(onFocus).not.toBeCalled();
expect(onBlur).not.toBeCalled();
});
});
describe('onBlur', () => {
let onBlur, ref;
const componentInit = () => {
onBlur = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useFocus({
onBlur,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called after "blur" event', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
target.blur();
expect(onBlur).toHaveBeenCalledTimes(1);
});
});
describe('onFocus', () => {
let onFocus, ref, innerRef;
const componentInit = () => {
onFocus = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
const Component = () => {
const listener = useFocus({
onFocus,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
<a ref={innerRef} />
</div>
);
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called after "focus" event', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
expect(onFocus).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('is not called if descendants of target receive focus', () => {
componentInit();
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocus).not.toBeCalled();
});
// @gate experimental
it('is called with the correct pointerType: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown();
target.pointerup();
expect(onFocus).toHaveBeenCalledTimes(1);
expect(onFocus).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse'}),
);
});
// @gate experimental
it('is called with the correct pointerType: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
const pointerType = 'touch';
target.pointerdown({pointerType});
target.pointerup({pointerType});
expect(onFocus).toHaveBeenCalledTimes(1);
expect(onFocus).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch'}),
);
});
if (hasPointerEvents) {
// @gate experimental
it('is called with the correct pointerType: pen', () => {
componentInit();
const target = createEventTarget(ref.current);
const pointerType = 'pen';
target.pointerdown({pointerType});
target.pointerup({pointerType});
expect(onFocus).toHaveBeenCalledTimes(1);
expect(onFocus).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'pen'}),
);
});
}
// @gate experimental
it('is called with the correct pointerType using a keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'LeftArrow'});
target.focus();
expect(onFocus).toHaveBeenCalledTimes(1);
expect(onFocus).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'keyboard'}),
);
});
// @gate experimental
it('is called with the correct pointerType using Tab+altKey on Mac', () => {
platform.set('mac');
jest.resetModules();
initializeModules();
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Tab', altKey: true});
target.focus();
expect(onFocus).toHaveBeenCalledTimes(1);
expect(onFocus).toHaveBeenCalledWith(
expect.objectContaining({
pointerType: 'keyboard',
}),
);
platform.clear();
});
});
describe('onFocusChange', () => {
let onFocusChange, ref, innerRef;
const componentInit = () => {
onFocusChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
const Component = () => {
const listener = useFocus({
onFocusChange,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
<div ref={innerRef} />
</div>
);
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called after "blur" and "focus" events', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
expect(onFocusChange).toHaveBeenCalledTimes(1);
expect(onFocusChange).toHaveBeenCalledWith(true);
target.blur();
expect(onFocusChange).toHaveBeenCalledTimes(2);
expect(onFocusChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is not called after "blur" and "focus" events on descendants', () => {
componentInit();
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocusChange).toHaveBeenCalledTimes(0);
target.blur();
expect(onFocusChange).toHaveBeenCalledTimes(0);
});
});
describe('onFocusVisibleChange', () => {
let onFocusVisibleChange, ref, innerRef;
const componentInit = () => {
onFocusVisibleChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
const Component = () => {
const listener = useFocus({
onFocusVisibleChange,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
<div ref={innerRef} />
</div>
);
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called after "focus" and "blur" if keyboard navigation is active', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
// use keyboard first
containerTarget.keydown({key: 'Tab'});
target.focus();
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
target.blur({relatedTarget: container});
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
// use keyboard first
containerTarget.keydown({key: 'Tab'});
target.focus();
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
// then use pointer on the target, focus should no longer be visible
target.pointerdown();
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
// onFocusVisibleChange should not be called again
target.blur({relatedTarget: container});
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
});
// @gate experimental
it('is not called after "focus" and "blur" events without keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
target.pointerdown();
target.pointerup();
containerTarget.pointerdown();
target.blur({relatedTarget: container});
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('is not called after "blur" and "focus" events on descendants', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current);
const containerTarget = createEventTarget(container);
containerTarget.keydown({key: 'Tab'});
innerTarget.focus();
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
innerTarget.blur({relatedTarget: container});
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
});
});
describe('nested Focus components', () => {
// @gate experimental
it('propagates events in the correct order', () => {
const events = [];
const innerRef = React.createRef();
const outerRef = React.createRef();
const createEventHandler = msg => () => {
events.push(msg);
};
const Inner = () => {
const listener = useFocus({
onBlur: createEventHandler('inner: onBlur'),
onFocus: createEventHandler('inner: onFocus'),
onFocusChange: createEventHandler('inner: onFocusChange'),
});
return <div ref={innerRef} DEPRECATED_flareListeners={listener} />;
};
const Outer = () => {
const listener = useFocus({
onBlur: createEventHandler('outer: onBlur'),
onFocus: createEventHandler('outer: onFocus'),
onFocusChange: createEventHandler('outer: onFocusChange'),
});
return (
<div ref={outerRef} DEPRECATED_flareListeners={listener}>
<Inner />
</div>
);
};
ReactDOM.render(<Outer />, container);
const innerTarget = createEventTarget(innerRef.current);
const outerTarget = createEventTarget(outerRef.current);
outerTarget.focus();
outerTarget.blur();
innerTarget.focus();
innerTarget.blur();
expect(events).toEqual([
'outer: onFocus',
'outer: onFocusChange',
'outer: onBlur',
'outer: onFocusChange',
'inner: onFocus',
'inner: onFocusChange',
'inner: onBlur',
'inner: onFocusChange',
]);
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(FocusResponder.displayName).toBe('Focus');
});
});

View File

@ -1,574 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';
let React;
let ReactFeatureFlags;
let ReactDOM;
let FocusWithinResponder;
let useFocusWithin;
let ReactTestRenderer;
let act;
const initializeModules = hasPointerEvents => {
setPointerEvent(hasPointerEvents);
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
ReactFeatureFlags.enableScopeAPI = true;
React = require('react');
ReactDOM = require('react-dom');
ReactTestRenderer = require('react-test-renderer');
act = ReactTestRenderer.act;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
FocusWithinResponder = require('react-interactions/events/deprecated-focus')
.FocusWithinResponder;
useFocusWithin = require('react-interactions/events/deprecated-focus')
.useFocusWithin;
}
};
const forcePointerEvents = true;
const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('FocusWithin responder', hasPointerEvents => {
let container;
let container2;
beforeEach(() => {
initializeModules();
container = document.createElement('div');
document.body.appendChild(container);
container2 = document.createElement('div');
document.body.appendChild(container2);
});
afterEach(() => {
ReactDOM.render(null, container);
document.body.removeChild(container);
document.body.removeChild(container2);
container = null;
container2 = null;
});
describe('disabled', () => {
let onFocusWithinChange, onFocusWithinVisibleChange, ref;
const componentInit = () => {
onFocusWithinChange = jest.fn();
onFocusWithinVisibleChange = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useFocusWithin({
disabled: true,
onFocusWithinChange,
onFocusWithinVisibleChange,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('prevents custom events being dispatched', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
target.blur();
expect(onFocusWithinChange).not.toBeCalled();
expect(onFocusWithinVisibleChange).not.toBeCalled();
});
});
describe('onFocusWithinChange', () => {
let onFocusWithinChange, ref, innerRef, innerRef2;
const Component = ({show}) => {
const listener = useFocusWithin({
onFocusWithinChange,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
{show && <input ref={innerRef} />}
<div ref={innerRef2} />
</div>
);
};
const componentInit = () => {
onFocusWithinChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
ReactDOM.render(<Component show={true} />, container);
};
// @gate experimental
it('is called after "blur" and "focus" events on focus target', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
target.blur({relatedTarget: container});
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called after "blur" and "focus" events on descendants', () => {
componentInit();
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
target.blur({relatedTarget: container});
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is only called once when focus moves within and outside the subtree', () => {
componentInit();
const node = ref.current;
const innerNode1 = innerRef.current;
const innerNode2 = innerRef.current;
const target = createEventTarget(node);
const innerTarget1 = createEventTarget(innerNode1);
const innerTarget2 = createEventTarget(innerNode2);
// focus shifts into subtree
innerTarget1.focus();
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
// focus moves around subtree
innerTarget1.blur({relatedTarget: innerNode2});
innerTarget2.focus();
innerTarget2.blur({relatedTarget: node});
target.focus();
target.blur({relatedTarget: innerNode1});
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
// focus shifts outside subtree
innerTarget1.blur({relatedTarget: container});
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
});
describe('onFocusWithinVisibleChange', () => {
let onFocusWithinVisibleChange, ref, innerRef, innerRef2;
const Component = ({show}) => {
const listener = useFocusWithin({
onFocusWithinVisibleChange,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
{show && <input ref={innerRef} />}
<div ref={innerRef2} />
</div>
);
};
const componentInit = () => {
onFocusWithinVisibleChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
ReactDOM.render(<Component show={true} />, container);
};
// @gate experimental
it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
// use keyboard first
containerTarget.keydown({key: 'Tab'});
target.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
target.blur({relatedTarget: container});
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called after "focus" and "blur" on descendants if keyboard was used', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current);
const containerTarget = createEventTarget(container);
// use keyboard first
containerTarget.keydown({key: 'Tab'});
innerTarget.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
innerTarget.blur({relatedTarget: container});
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
componentInit();
const node = ref.current;
const innerNode1 = innerRef.current;
const innerNode2 = innerRef2.current;
const target = createEventTarget(node);
const innerTarget1 = createEventTarget(innerNode1);
const innerTarget2 = createEventTarget(innerNode2);
// use keyboard first
target.focus();
target.keydown({key: 'Tab'});
target.blur({relatedTarget: innerNode1});
innerTarget1.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
// then use pointer on the next target, focus should no longer be visible
innerTarget2.pointerdown();
innerTarget1.blur({relatedTarget: innerNode2});
innerTarget2.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
// then use keyboard again
innerTarget2.keydown({key: 'Tab', shiftKey: true});
innerTarget2.blur({relatedTarget: innerNode1});
innerTarget1.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
// then use pointer on the target, focus should no longer be visible
innerTarget1.pointerdown();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
// onFocusVisibleChange should not be called again
innerTarget1.blur({relatedTarget: container});
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
});
// @gate experimental
it('is not called after "focus" and "blur" events without keyboard', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current);
innerTarget.pointerdown();
innerTarget.pointerup();
innerTarget.blur({relatedTarget: container});
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('is only called once when focus moves within and outside the subtree', () => {
componentInit();
const node = ref.current;
const innerNode1 = innerRef.current;
const innerNode2 = innerRef2.current;
const target = createEventTarget(node);
const innerTarget1 = createEventTarget(innerNode1);
const innerTarget2 = createEventTarget(innerNode2);
// focus shifts into subtree
innerTarget1.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
// focus moves around subtree
innerTarget1.blur({relatedTarget: innerNode2});
innerTarget2.focus();
innerTarget2.blur({relatedTarget: node});
target.focus();
target.blur({relatedTarget: innerNode1});
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
// focus shifts outside subtree
innerTarget1.blur({relatedTarget: container});
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
});
describe('onBeforeBlurWithin', () => {
let onBeforeBlurWithin, onAfterBlurWithin, ref, innerRef, innerRef2;
beforeEach(() => {
onBeforeBlurWithin = jest.fn();
onAfterBlurWithin = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
});
// @gate experimental
it('is called after a focused element is unmounted', () => {
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onAfterBlurWithin,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
{show && <input ref={innerRef} />}
<div ref={innerRef2} />
</div>
);
};
ReactDOM.render(<Component show={true} />, container);
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
ReactDOM.render(<Component show={false} />, container);
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledWith(
expect.objectContaining({relatedTarget: inner}),
);
});
// @gate experimental
it('is called after a nested focused element is unmounted', () => {
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onAfterBlurWithin,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
{show && (
<div>
<input ref={innerRef} />
</div>
)}
<div ref={innerRef2} />
</div>
);
};
ReactDOM.render(<Component show={true} />, container);
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
ReactDOM.render(<Component show={false} />, container);
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledWith(
expect.objectContaining({relatedTarget: inner}),
);
});
// @gate experimental
it('is called after many elements are unmounted', () => {
const buttonRef = React.createRef();
const inputRef = React.createRef();
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onAfterBlurWithin,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
{show && <button>Press me!</button>}
{show && <button>Press me!</button>}
{show && <input ref={inputRef} />}
{show && <button>Press me!</button>}
{!show && <button ref={buttonRef}>Press me!</button>}
{show && <button>Press me!</button>}
<button>Press me!</button>
<button>Press me!</button>
</div>
);
};
ReactDOM.render(<Component show={true} />, container);
inputRef.current.focus();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
ReactDOM.render(<Component show={false} />, container);
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('is called after a nested focused element is unmounted (with scope query)', () => {
const TestScope = React.unstable_Scope;
const testScopeQuery = (type, props) => true;
let targetNodes;
let targetNode;
const Component = ({show}) => {
const scopeRef = React.useRef(null);
const listener = useFocusWithin({
onBeforeBlurWithin(event) {
const scope = scopeRef.current;
targetNode = innerRef.current;
targetNodes = scope.DO_NOT_USE_queryAllNodes(testScopeQuery);
},
});
return (
<TestScope ref={scopeRef} DEPRECATED_flareListeners={[listener]}>
{show && <input ref={innerRef} />}
</TestScope>
);
};
ReactDOM.render(<Component show={true} />, container);
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
ReactDOM.render(<Component show={false} />, container);
expect(targetNodes).toEqual([targetNode]);
});
// @gate experimental
it('is called after a focused suspended element is hidden', () => {
const Suspense = React.Suspense;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
function Child() {
if (suspend) {
throw promise;
} else {
return <input ref={innerRef} />;
}
}
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onAfterBlurWithin,
});
return (
<div DEPRECATED_flareListeners={listener}>
<Suspense fallback="Loading...">
<Child />
</Suspense>
</div>
);
};
const root = ReactDOM.createRoot(container2);
act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(container2.innerHTML).toBe('<div><input></div>');
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
suspend = true;
act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(container2.innerHTML).toBe(
'<div><input style="display: none;">Loading...</div>',
);
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
resolve();
});
// @gate experimental
it('is called after a focused suspended element is hidden then shown', () => {
const Suspense = React.Suspense;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
const buttonRef = React.createRef();
function Child() {
if (suspend) {
throw promise;
} else {
return <input ref={innerRef} />;
}
}
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onAfterBlurWithin,
});
return (
<div ref={ref} DEPRECATED_flareListeners={listener}>
<Suspense fallback={<button ref={buttonRef}>Loading...</button>}>
<Child />
</Suspense>
</div>
);
};
const root = ReactDOM.createRoot(container2);
act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
suspend = true;
act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
buttonRef.current.focus();
suspend = false;
act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
resolve();
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(FocusWithinResponder.displayName).toBe('FocusWithin');
});
});

View File

@ -1,430 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';
let React;
let ReactFeatureFlags;
let ReactDOM;
let HoverResponder;
let useHover;
function initializeModules(hasPointerEvents) {
jest.resetModules();
setPointerEvent(hasPointerEvents);
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
HoverResponder = require('react-interactions/events/hover').HoverResponder;
useHover = require('react-interactions/events/hover').useHover;
}
}
const forcePointerEvents = true;
const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('Hover responder', hasPointerEvents => {
let container;
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
ReactDOM.render(null, container);
document.body.removeChild(container);
container = null;
});
describe('disabled', () => {
let onHoverChange, onHoverStart, onHoverMove, onHoverEnd, ref;
const componentInit = () => {
onHoverChange = jest.fn();
onHoverStart = jest.fn();
onHoverMove = jest.fn();
onHoverEnd = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useHover({
disabled: true,
onHoverChange,
onHoverStart,
onHoverMove,
onHoverEnd,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointerexit();
expect(onHoverChange).not.toBeCalled();
expect(onHoverStart).not.toBeCalled();
expect(onHoverMove).not.toBeCalled();
expect(onHoverEnd).not.toBeCalled();
});
});
describe('onHoverStart', () => {
let onHoverStart, ref;
const componentInit = () => {
onHoverStart = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useHover({
onHoverStart: onHoverStart,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called for mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
expect(onHoverStart).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('is not called for touch pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onHoverStart).not.toBeCalled();
});
// @gate experimental
it('is called if a mouse pointer is used after a touch pointer', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
target.pointerenter();
expect(onHoverStart).toHaveBeenCalledTimes(1);
});
});
describe('onHoverChange', () => {
let onHoverChange, ref;
const componentInit = () => {
onHoverChange = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useHover({
onHoverChange,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called for mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
expect(onHoverChange).toHaveBeenCalledTimes(1);
expect(onHoverChange).toHaveBeenCalledWith(true);
target.pointerexit();
expect(onHoverChange).toHaveBeenCalledTimes(2);
expect(onHoverChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is not called for touch pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onHoverChange).not.toBeCalled();
});
});
describe('onHoverEnd', () => {
let onHoverEnd, ref;
const componentInit = () => {
onHoverEnd = jest.fn();
ref = React.createRef();
const Component = () => {
const listener = useHover({
onHoverEnd,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
};
// @gate experimental
it('is called for mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointerexit();
expect(onHoverEnd).toHaveBeenCalledTimes(1);
});
if (hasPointerEvents) {
// @gate experimental
it('is called once for cancelled mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointercancel();
expect(onHoverEnd).toHaveBeenCalledTimes(1);
// only called once if cancel follows exit
onHoverEnd.mockReset();
target.pointerenter();
target.pointerexit();
target.pointercancel();
expect(onHoverEnd).toHaveBeenCalledTimes(1);
});
}
// @gate experimental
it('is not called for touch pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onHoverEnd).not.toBeCalled();
});
// @gate experimental
it('should correctly work with React Portals', () => {
componentInit();
const portalNode = document.createElement('div');
const divRef = React.createRef();
const spanRef = React.createRef();
function Test() {
const listener = useHover({
onHoverEnd,
});
return (
<div ref={divRef} DEPRECATED_flareListeners={listener}>
{ReactDOM.createPortal(<span ref={spanRef} />, portalNode)}
</div>
);
}
ReactDOM.render(<Test />, container);
const div = createEventTarget(divRef.current);
div.pointerenter();
const span = createEventTarget(spanRef.current);
span.pointerexit();
expect(onHoverEnd).not.toBeCalled();
const body = createEventTarget(document.body);
body.pointerexit();
expect(onHoverEnd).toBeCalled();
});
});
describe('onHoverMove', () => {
// @gate experimental
it('is called after the active pointer moves"', () => {
const onHoverMove = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = useHover({
onHoverMove,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointerhover({x: 0, y: 0});
target.pointerhover({x: 1, y: 1});
expect(onHoverMove).toHaveBeenCalledTimes(2);
expect(onHoverMove).toHaveBeenCalledWith(
expect.objectContaining({type: 'hovermove'}),
);
});
});
describe('nested Hover components', () => {
// @gate experimental
it('not propagate by default', () => {
const events = [];
const innerRef = React.createRef();
const outerRef = React.createRef();
const createEventHandler = msg => () => {
events.push(msg);
};
const Inner = () => {
const listener = useHover({
onHoverStart: createEventHandler('inner: onHoverStart'),
onHoverEnd: createEventHandler('inner: onHoverEnd'),
onHoverChange: createEventHandler('inner: onHoverChange'),
});
return <div ref={innerRef} DEPRECATED_flareListeners={listener} />;
};
const Outer = () => {
const listener = useHover({
onHoverStart: createEventHandler('outer: onHoverStart'),
onHoverEnd: createEventHandler('outer: onHoverEnd'),
onHoverChange: createEventHandler('outer: onHoverChange'),
});
return (
<div ref={outerRef} DEPRECATED_flareListeners={listener}>
<Inner />
</div>
);
};
ReactDOM.render(<Outer />, container);
const innerNode = innerRef.current;
const outerNode = outerRef.current;
const innerTarget = createEventTarget(innerNode);
const outerTarget = createEventTarget(outerNode);
outerTarget.pointerenter({relatedTarget: container});
outerTarget.pointerexit({relatedTarget: innerNode});
innerTarget.pointerenter({relatedTarget: outerNode});
innerTarget.pointerexit({relatedTarget: outerNode});
outerTarget.pointerenter({relatedTarget: innerNode});
outerTarget.pointerexit({relatedTarget: container});
expect(events).toEqual([
'outer: onHoverStart',
'outer: onHoverChange',
'outer: onHoverEnd',
'outer: onHoverChange',
'inner: onHoverStart',
'inner: onHoverChange',
'inner: onHoverEnd',
'inner: onHoverChange',
'outer: onHoverStart',
'outer: onHoverChange',
'outer: onHoverEnd',
'outer: onHoverChange',
]);
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(HoverResponder.displayName).toBe('Hover');
});
// @gate experimental
it('should correctly pass through event properties', () => {
const timeStamps = [];
const ref = React.createRef();
const eventLog = [];
const logEvent = event => {
const propertiesWeCareAbout = {
x: event.x,
y: event.y,
pageX: event.pageX,
pageY: event.pageY,
clientX: event.clientX,
clientY: event.clientY,
pointerType: event.pointerType,
target: event.target,
timeStamp: event.timeStamp,
type: event.type,
};
timeStamps.push(event.timeStamp);
eventLog.push(propertiesWeCareAbout);
};
const Component = () => {
const listener = useHover({
onHoverStart: logEvent,
onHoverEnd: logEvent,
onHoverMove: logEvent,
});
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const node = ref.current;
const target = createEventTarget(node);
target.pointerenter({x: 10, y: 10});
target.pointerhover({x: 10, y: 10});
target.pointerhover({x: 20, y: 20});
target.pointerexit({x: 20, y: 20});
expect(eventLog).toEqual([
{
x: 10,
y: 10,
pageX: 10,
pageY: 10,
clientX: 10,
clientY: 10,
target: node,
timeStamp: timeStamps[0],
type: 'hoverstart',
pointerType: 'mouse',
},
{
x: 10,
y: 10,
pageX: 10,
pageY: 10,
clientX: 10,
clientY: 10,
target: node,
timeStamp: timeStamps[1],
type: 'hovermove',
pointerType: 'mouse',
},
{
x: 20,
y: 20,
pageX: 20,
pageY: 20,
clientX: 20,
clientY: 20,
target: node,
timeStamp: timeStamps[2],
type: 'hovermove',
pointerType: 'mouse',
},
{
x: 20,
y: 20,
pageX: 20,
pageY: 20,
clientX: 20,
clientY: 20,
target: node,
timeStamp: timeStamps[3],
type: 'hoverend',
pointerType: 'mouse',
},
]);
});
});

View File

@ -1,255 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
import {createEventTarget} from 'dom-event-testing-library';
let React;
let ReactFeatureFlags;
let ReactDOM;
let Scheduler;
describe('mixing responders with the heritage event system', () => {
let container;
beforeEach(() => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
// @gate experimental
it('should properly only flush sync once when the event systems are mixed', () => {
const useTap = require('react-interactions/events/tap').useTap;
const ref = React.createRef();
let renderCounts = 0;
function MyComponent() {
const [, updateCounter] = React.useState(0);
renderCounts++;
function handleTap() {
updateCounter(count => count + 1);
}
const listener = useTap({
onTapEnd: handleTap,
});
return (
<div>
<button
ref={ref}
DEPRECATED_flareListeners={listener}
onClick={() => {
updateCounter(count => count + 1);
}}>
Press me
</button>
</div>
);
}
const newContainer = document.createElement('div');
const root = ReactDOM.createRoot(newContainer);
document.body.appendChild(newContainer);
root.render(<MyComponent />);
Scheduler.unstable_flushAll();
const target = createEventTarget(ref.current);
target.pointerdown({timeStamp: 100});
target.pointerup({timeStamp: 100});
target.click({timeStamp: 100});
if (__DEV__) {
expect(renderCounts).toBe(2);
} else {
expect(renderCounts).toBe(1);
}
Scheduler.unstable_flushAll();
if (__DEV__) {
expect(renderCounts).toBe(4);
} else {
expect(renderCounts).toBe(2);
}
target.pointerdown({timeStamp: 100});
target.pointerup({timeStamp: 100});
// Ensure the timeStamp logic works
target.click({timeStamp: 101});
if (__DEV__) {
expect(renderCounts).toBe(6);
} else {
expect(renderCounts).toBe(3);
}
Scheduler.unstable_flushAll();
document.body.removeChild(newContainer);
});
// @gate experimental
it(
'should only flush before outermost discrete event handler when mixing ' +
'event systems',
async () => {
const {useState} = React;
const useTap = require('react-interactions/events/tap').useTap;
const button = React.createRef();
function MyComponent() {
const [pressesCount, updatePressesCount] = useState(0);
const [clicksCount, updateClicksCount] = useState(0);
function handleTap() {
// This dispatches a synchronous, discrete event in the legacy event
// system. However, because it's nested inside the new event system,
// its updates should not flush until the end of the outer handler.
const target = createEventTarget(button.current);
target.click();
// Text context should not have changed
Scheduler.unstable_yieldValue(newContainer.textContent);
updatePressesCount(pressesCount + 1);
}
const tap = useTap({
onTapEnd: handleTap,
});
return (
<div>
<button
DEPRECATED_flareListeners={tap}
ref={button}
onClick={() => updateClicksCount(clicksCount + 1)}>
Presses: {pressesCount}, Clicks: {clicksCount}
</button>
</div>
);
}
const newContainer = document.createElement('div');
document.body.appendChild(newContainer);
const root = ReactDOM.createRoot(newContainer);
root.render(<MyComponent />);
expect(Scheduler).toFlushWithoutYielding();
expect(newContainer.textContent).toEqual('Presses: 0, Clicks: 0');
const target = createEventTarget(button.current);
target.pointerdown({timeStamp: 100});
target.pointerup({timeStamp: 100});
expect(Scheduler).toHaveYielded(['Presses: 0, Clicks: 0']);
expect(Scheduler).toFlushWithoutYielding();
expect(newContainer.textContent).toEqual('Presses: 1, Clicks: 1');
},
);
describe('mixing the Input and Press responders', () => {
// @gate experimental
it('is async for non-input events', () => {
const useTap = require('react-interactions/events/tap').useTap;
const useInput = require('react-interactions/events/input').useInput;
const root = ReactDOM.createRoot(container);
let input;
function Component({innerRef, onChange, controlledValue, listeners}) {
const inputListener = useInput({onChange});
return (
<input
type="text"
ref={innerRef}
value={controlledValue}
DEPRECATED_flareListeners={[inputListener, listeners]}
/>
);
}
function PressWrapper({innerRef, onTap, onChange, controlledValue}) {
const tap = useTap({
onTapEnd: onTap,
});
return (
<Component
onChange={onChange}
innerRef={el => (input = el)}
controlledValue={controlledValue}
listeners={tap}
/>
);
}
class ControlledInput extends React.Component {
state = {value: 'initial'};
onChange = event => this.setState({value: event.target.value});
reset = () => {
this.setState({value: ''});
};
render() {
Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
const controlledValue =
this.state.value === 'changed' ? 'changed [!]' : this.state.value;
return (
<PressWrapper
onTap={this.reset}
onChange={this.onChange}
innerRef={el => (input = el)}
controlledValue={controlledValue}
/>
);
}
}
// Initial mount. Test that this is async.
root.render(<ControlledInput />);
// Should not have flushed yet.
expect(Scheduler).toHaveYielded([]);
expect(input).toBe(undefined);
// Flush callbacks.
expect(Scheduler).toFlushAndYield(['render: initial']);
expect(input.value).toBe('initial');
// Trigger a click event
input.dispatchEvent(
new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
buttons: 1,
}),
);
input.dispatchEvent(
new MouseEvent('mouseup', {
bubbles: true,
cancelable: true,
buttons: 0,
}),
);
// Nothing should have changed
expect(Scheduler).toHaveYielded([]);
expect(input.value).toBe('initial');
// Flush callbacks.
// Now the click update has flushed.
expect(Scheduler).toFlushAndYield(['render: ']);
expect(input.value).toBe('');
});
});
});

View File

@ -1,87 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
import type {
ReactDOMResponderContext,
ReactDOMResponderEvent,
} from 'react-dom/src/shared/ReactDOMTypes';
export const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent !== undefined;
export const isMac =
typeof window !== 'undefined' && window.navigator != null
? /^Mac/.test(window.navigator.platform)
: false;
export const buttonsEnum = {
none: 0,
primary: 1,
secondary: 2,
auxiliary: 4,
};
export function dispatchDiscreteEvent(
context: ReactDOMResponderContext,
payload: ReactDOMResponderEvent,
callback: any => void,
) {
context.dispatchEvent(payload, callback, DiscreteEvent);
}
export function dispatchUserBlockingEvent(
context: ReactDOMResponderContext,
payload: ReactDOMResponderEvent,
callback: any => void,
) {
context.dispatchEvent(payload, callback, UserBlockingEvent);
}
export function getTouchById(
nativeEvent: TouchEvent,
pointerId: null | number,
): null | Touch {
if (pointerId != null) {
const changedTouches = nativeEvent.changedTouches;
for (let i = 0; i < changedTouches.length; i++) {
const touch = changedTouches[i];
if (touch.identifier === pointerId) {
return touch;
}
}
return null;
}
return null;
}
export function hasModifierKey(event: ReactDOMResponderEvent): boolean {
const nativeEvent: any = event.nativeEvent;
const {altKey, ctrlKey, metaKey, shiftKey} = nativeEvent;
return (
altKey === true || ctrlKey === true || metaKey === true || shiftKey === true
);
}
// Keyboards, Assitive Technologies, and element.click() all produce a "virtual"
// click event. This is a method of inferring such clicks. Every browser except
// IE 11 only sets a zero value of "detail" for click events that are "virtual".
// However, IE 11 uses a zero value for all click events. For IE 11 we rely on
// the quirk that it produces click events that are of type PointerEvent, and
// where only the "virtual" click lacks a pointerType field.
export function isVirtualClick(event: ReactDOMResponderEvent): boolean {
const nativeEvent: any = event.nativeEvent;
// JAWS/NVDA with Firefox.
if (nativeEvent.mozInputSource === 0 && nativeEvent.isTrusted) {
return true;
}
return nativeEvent.detail === 0 && !nativeEvent.pointerType;
}

View File

@ -1,10 +0,0 @@
/**
* 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
*/
export * from './src/dom/Tap';

View File

@ -435,22 +435,6 @@ export function replaceContainerChildren(
newChildren: ChildSet,
): void {}
export function DEPRECATED_mountResponderInstance(
responder: any,
responderInstance: any,
props: Object,
state: Object,
instance: Instance,
) {
throw new Error('Not yet implemented.');
}
export function DEPRECATED_unmountResponderInstance(
responderInstance: any,
): void {
throw new Error('Not yet implemented.');
}
export function getFundamentalComponentInstance(fundamentalInstance: any) {
throw new Error('Not yet implemented.');
}

View File

@ -492,22 +492,6 @@ export function unhideTextInstance(
throw new Error('Not yet implemented.');
}
export function DEPRECATED_mountResponderInstance(
responder: any,
responderInstance: any,
props: Object,
state: Object,
instance: Instance,
) {
throw new Error('Not yet implemented.');
}
export function DEPRECATED_unmountResponderInstance(
responderInstance: any,
): void {
throw new Error('Not yet implemented.');
}
export function getFundamentalComponentInstance(fundamentalInstance: any) {
throw new Error('Not yet implemented.');
}

View File

@ -13,7 +13,6 @@
// though the responder plugin is only used in React Native. Sadness ensues.
// The coverage is valuable though, so we will keep it for now.
const {HostComponent} = require('react-reconciler/src/ReactWorkTags');
const {PLUGIN_EVENT_SYSTEM} = require('react-dom/src/events/EventSystemFlags');
let EventBatching;
let EventPluginUtils;
@ -320,7 +319,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) {
nativeEventConfig.targetInst,
nativeEventConfig.nativeEvent,
nativeEventConfig.target,
PLUGIN_EVENT_SYSTEM,
0,
);
// At this point the negotiation events have been dispatched as part of the

View File

@ -64,7 +64,7 @@ export function discreteUpdates(fn, a, b, c, d) {
}
}
export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
export function flushDiscreteUpdatesIfNeeded() {
if (!isInsideEventHandler) {
flushDiscreteUpdatesImpl();
}

View File

@ -375,14 +375,6 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
warnsIfNotActing: true,
supportsHydration: false,
DEPRECATED_mountResponderInstance(): void {
// NO-OP
},
DEPRECATED_unmountResponderInstance(): void {
// NO-OP
},
getFundamentalComponentInstance(fundamentalInstance): Instance {
const {impl, props, state} = fundamentalInstance;
return impl.getInstance(null, props, state);

View File

@ -326,7 +326,6 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
// These will be overridden during the parent's reconciliation
@ -422,7 +421,6 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
if (enableProfilerTimer) {

View File

@ -320,7 +320,6 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
// These will be overridden during the parent's reconciliation
@ -416,7 +415,6 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
if (enableProfilerTimer) {

View File

@ -31,7 +31,6 @@ import {
enableProfilerTimer,
enableProfilerCommitHooks,
enableSuspenseServerRenderer,
enableDeprecatedFlareAPI,
enableFundamentalAPI,
enableSuspenseCallback,
enableScopeAPI,
@ -124,10 +123,6 @@ import {
Layout as HookLayout,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
import {
updateDeprecatedEventListeners,
unmountDeprecatedResponderListeners,
} from './ReactFiberDeprecatedEvents.new';
import {
NoEffect as NoSubtreeTag,
Passive as PassiveSubtreeTag,
@ -904,9 +899,6 @@ function commitUnmount(
return;
}
case HostComponent: {
if (enableDeprecatedFlareAPI) {
unmountDeprecatedResponderListeners(current);
}
safelyDetachRef(current);
return;
}
@ -945,9 +937,6 @@ function commitUnmount(
}
case ScopeComponent: {
if (enableScopeAPI) {
if (enableDeprecatedFlareAPI) {
unmountDeprecatedResponderListeners(current);
}
safelyDetachRef(current);
}
return;
@ -1533,13 +1522,6 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
finishedWork,
);
}
if (enableDeprecatedFlareAPI) {
const prevListeners = oldProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
updateDeprecatedEventListeners(nextListeners, finishedWork, null);
}
}
}
return;
}
@ -1596,15 +1578,6 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
case ScopeComponent: {
if (enableScopeAPI) {
const scopeInstance = finishedWork.stateNode;
if (enableDeprecatedFlareAPI) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const prevListeners = oldProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners || current === null) {
updateDeprecatedEventListeners(nextListeners, finishedWork, null);
}
}
prepareScopeUpdate(scopeInstance, finishedWork);
return;
}

View File

@ -31,7 +31,6 @@ import {
enableProfilerTimer,
enableProfilerCommitHooks,
enableSuspenseServerRenderer,
enableDeprecatedFlareAPI,
enableFundamentalAPI,
enableSuspenseCallback,
enableScopeAPI,
@ -126,10 +125,6 @@ import {
Passive as HookPassive,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
import {
updateDeprecatedEventListeners,
unmountDeprecatedResponderListeners,
} from './ReactFiberDeprecatedEvents.old';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
@ -922,9 +917,6 @@ function commitUnmount(
return;
}
case HostComponent: {
if (enableDeprecatedFlareAPI) {
unmountDeprecatedResponderListeners(current);
}
safelyDetachRef(current);
return;
}
@ -963,9 +955,6 @@ function commitUnmount(
}
case ScopeComponent: {
if (enableScopeAPI) {
if (enableDeprecatedFlareAPI) {
unmountDeprecatedResponderListeners(current);
}
safelyDetachRef(current);
}
return;
@ -1556,13 +1545,6 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
finishedWork,
);
}
if (enableDeprecatedFlareAPI) {
const prevListeners = oldProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
updateDeprecatedEventListeners(nextListeners, finishedWork, null);
}
}
}
return;
}
@ -1619,15 +1601,6 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
case ScopeComponent: {
if (enableScopeAPI) {
const scopeInstance = finishedWork.stateNode;
if (enableDeprecatedFlareAPI) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const prevListeners = oldProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners || current === null) {
updateDeprecatedEventListeners(nextListeners, finishedWork, null);
}
}
prepareScopeUpdate(scopeInstance, finishedWork);
return;
}

View File

@ -126,7 +126,6 @@ import {
enableSchedulerTracing,
enableSuspenseCallback,
enableSuspenseServerRenderer,
enableDeprecatedFlareAPI,
enableFundamentalAPI,
enableScopeAPI,
enableBlocksAPI,
@ -142,7 +141,6 @@ import {
import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
import {OffscreenLane} from './ReactFiberLane';
import {resetChildFibers} from './ReactChildFiber.new';
import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.new';
import {createScopeInstance} from './ReactFiberScope.new';
import {transferActualDuration} from './ReactProfilerTimer.new';
@ -737,14 +735,6 @@ function completeWork(
rootContainerInstance,
);
if (enableDeprecatedFlareAPI) {
const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
@ -779,16 +769,6 @@ function completeWork(
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
} else {
const instance = createInstance(
type,
@ -800,20 +780,8 @@ function completeWork(
appendAllChildren(instance, workInProgress, false, false);
// This needs to be set before we mount Flare event listeners
workInProgress.stateNode = instance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
@ -1289,37 +1257,14 @@ function completeWork(
if (current === null) {
const scopeInstance: ReactScopeInstance = createScopeInstance();
workInProgress.stateNode = scopeInstance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
const rootContainerInstance = getRootHostContainer();
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
prepareScopeUpdate(scopeInstance, workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
markUpdate(workInProgress);
}
} else {
if (enableDeprecatedFlareAPI) {
const prevListeners =
current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (
prevListeners !== nextListeners ||
workInProgress.ref !== null
) {
markUpdate(workInProgress);
}
} else {
if (workInProgress.ref !== null) {
markUpdate(workInProgress);
}
if (workInProgress.ref !== null) {
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);

View File

@ -124,7 +124,6 @@ import {
enableSchedulerTracing,
enableSuspenseCallback,
enableSuspenseServerRenderer,
enableDeprecatedFlareAPI,
enableFundamentalAPI,
enableScopeAPI,
enableBlocksAPI,
@ -140,7 +139,6 @@ import {
import {createFundamentalStateInstance} from './ReactFiberFundamental.old';
import {OffscreenLane} from './ReactFiberLane';
import {resetChildFibers} from './ReactChildFiber.old';
import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.old';
import {createScopeInstance} from './ReactFiberScope.old';
import {transferActualDuration} from './ReactProfilerTimer.old';
@ -716,14 +714,6 @@ function completeWork(
rootContainerInstance,
);
if (enableDeprecatedFlareAPI) {
const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
@ -758,16 +748,6 @@ function completeWork(
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
} else {
const instance = createInstance(
type,
@ -779,20 +759,8 @@ function completeWork(
appendAllChildren(instance, workInProgress, false, false);
// This needs to be set before we mount Flare event listeners
workInProgress.stateNode = instance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
@ -1262,37 +1230,14 @@ function completeWork(
if (current === null) {
const scopeInstance: ReactScopeInstance = createScopeInstance();
workInProgress.stateNode = scopeInstance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
const rootContainerInstance = getRootHostContainer();
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
prepareScopeUpdate(scopeInstance, workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
markUpdate(workInProgress);
}
} else {
if (enableDeprecatedFlareAPI) {
const prevListeners =
current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (
prevListeners !== nextListeners ||
workInProgress.ref !== null
) {
markUpdate(workInProgress);
}
} else {
if (workInProgress.ref !== null) {
markUpdate(workInProgress);
}
if (workInProgress.ref !== null) {
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);

View File

@ -1,234 +0,0 @@
/**
* 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 {Fiber} from './ReactInternalTypes';
import type {Container, Instance} from './ReactFiberHostConfig';
import type {
ReactEventResponder,
ReactEventResponderInstance,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import {
DEPRECATED_mountResponderInstance,
DEPRECATED_unmountResponderInstance,
} from './ReactFiberHostConfig';
import {NoLanes} from './ReactFiberLane';
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
import invariant from 'shared/invariant';
import {HostComponent, HostRoot} from './ReactWorkTags';
const emptyObject = {};
const isArray = Array.isArray;
export function createResponderInstance(
responder: ReactEventResponder<any, any>,
responderProps: Object,
responderState: Object,
fiber: Fiber,
): ReactEventResponderInstance<any, any> {
return {
fiber,
props: responderProps,
responder,
rootEventTypes: null,
state: responderState,
};
}
function mountEventResponder(
responder: ReactEventResponder<any, any>,
responderProps: Object,
fiber: Fiber,
respondersMap: Map<
ReactEventResponder<any, any>,
ReactEventResponderInstance<any, any>,
>,
rootContainerInstance: null | Container,
) {
let responderState = emptyObject;
const getInitialState = responder.getInitialState;
if (getInitialState !== null) {
responderState = getInitialState(responderProps);
}
const responderInstance = createResponderInstance(
responder,
responderProps,
responderState,
fiber,
);
if (!rootContainerInstance) {
let node = fiber;
while (node !== null) {
const tag = node.tag;
if (tag === HostComponent) {
rootContainerInstance = node.stateNode;
break;
} else if (tag === HostRoot) {
rootContainerInstance = node.stateNode.containerInfo;
break;
}
node = node.return;
}
}
DEPRECATED_mountResponderInstance(
responder,
responderInstance,
responderProps,
responderState,
((rootContainerInstance: any): Instance),
);
respondersMap.set(responder, responderInstance);
}
function updateEventListener(
listener: ReactEventResponderListener<any, any>,
fiber: Fiber,
visitedResponders: Set<ReactEventResponder<any, any>>,
respondersMap: Map<
ReactEventResponder<any, any>,
ReactEventResponderInstance<any, any>,
>,
rootContainerInstance: null | Container,
): void {
let responder;
let props;
if (listener) {
responder = listener.responder;
props = listener.props;
}
invariant(
responder && responder.$$typeof === REACT_RESPONDER_TYPE,
'An invalid value was used as an event listener. Expect one or many event ' +
'listeners created via React.unstable_useResponder().',
);
const listenerProps = ((props: any): Object);
if (visitedResponders.has(responder)) {
// show warning
if (__DEV__) {
console.error(
'Duplicate event responder "%s" found in event listeners. ' +
'Event listeners passed to elements cannot use the same event responder more than once.',
responder.displayName,
);
}
return;
}
visitedResponders.add(responder);
const responderInstance = respondersMap.get(responder);
if (responderInstance === undefined) {
// Mount (happens in either complete or commit phase)
mountEventResponder(
responder,
listenerProps,
fiber,
respondersMap,
rootContainerInstance,
);
} else {
// Update (happens during commit phase only)
responderInstance.props = listenerProps;
responderInstance.fiber = fiber;
}
}
export function updateDeprecatedEventListeners(
listeners: any,
fiber: Fiber,
rootContainerInstance: null | Container,
): void {
const visitedResponders = new Set();
let dependencies = fiber.dependencies;
if (listeners != null) {
if (dependencies === null) {
dependencies = fiber.dependencies = {
lanes: NoLanes,
firstContext: null,
responders: new Map(),
};
}
let respondersMap = dependencies.responders;
if (respondersMap === null) {
dependencies.responders = respondersMap = new Map();
}
if (isArray(listeners)) {
for (let i = 0, length = listeners.length; i < length; i++) {
const listener = listeners[i];
updateEventListener(
listener,
fiber,
visitedResponders,
respondersMap,
rootContainerInstance,
);
}
} else {
updateEventListener(
listeners,
fiber,
visitedResponders,
respondersMap,
rootContainerInstance,
);
}
}
if (dependencies !== null) {
const respondersMap = dependencies.responders;
if (respondersMap !== null) {
// Unmount
const mountedResponders = Array.from(respondersMap.keys());
for (let i = 0, length = mountedResponders.length; i < length; i++) {
const mountedResponder = mountedResponders[i];
if (!visitedResponders.has(mountedResponder)) {
const responderInstance = ((respondersMap.get(
mountedResponder,
): any): ReactEventResponderInstance<any, any>);
DEPRECATED_unmountResponderInstance(responderInstance);
respondersMap.delete(mountedResponder);
}
}
}
}
}
export function createDeprecatedResponderListener(
responder: ReactEventResponder<any, any>,
props: Object,
): ReactEventResponderListener<any, any> {
const eventResponderListener = {
responder,
props,
};
if (__DEV__) {
Object.freeze(eventResponderListener);
}
return eventResponderListener;
}
export function unmountDeprecatedResponderListeners(fiber: Fiber) {
const dependencies = fiber.dependencies;
if (dependencies !== null) {
const respondersMap = dependencies.responders;
if (respondersMap !== null) {
const responderInstances = Array.from(respondersMap.values());
for (let i = 0, length = responderInstances.length; i < length; i++) {
const responderInstance = responderInstances[i];
DEPRECATED_unmountResponderInstance(responderInstance);
}
dependencies.responders = null;
}
}
}

View File

@ -1,234 +0,0 @@
/**
* 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 {Fiber} from './ReactInternalTypes';
import type {Container, Instance} from './ReactFiberHostConfig';
import type {
ReactEventResponder,
ReactEventResponderInstance,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import {
DEPRECATED_mountResponderInstance,
DEPRECATED_unmountResponderInstance,
} from './ReactFiberHostConfig';
import {NoLanes} from './ReactFiberLane';
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
import invariant from 'shared/invariant';
import {HostComponent, HostRoot} from './ReactWorkTags';
const emptyObject = {};
const isArray = Array.isArray;
export function createResponderInstance(
responder: ReactEventResponder<any, any>,
responderProps: Object,
responderState: Object,
fiber: Fiber,
): ReactEventResponderInstance<any, any> {
return {
fiber,
props: responderProps,
responder,
rootEventTypes: null,
state: responderState,
};
}
function mountEventResponder(
responder: ReactEventResponder<any, any>,
responderProps: Object,
fiber: Fiber,
respondersMap: Map<
ReactEventResponder<any, any>,
ReactEventResponderInstance<any, any>,
>,
rootContainerInstance: null | Container,
) {
let responderState = emptyObject;
const getInitialState = responder.getInitialState;
if (getInitialState !== null) {
responderState = getInitialState(responderProps);
}
const responderInstance = createResponderInstance(
responder,
responderProps,
responderState,
fiber,
);
if (!rootContainerInstance) {
let node = fiber;
while (node !== null) {
const tag = node.tag;
if (tag === HostComponent) {
rootContainerInstance = node.stateNode;
break;
} else if (tag === HostRoot) {
rootContainerInstance = node.stateNode.containerInfo;
break;
}
node = node.return;
}
}
DEPRECATED_mountResponderInstance(
responder,
responderInstance,
responderProps,
responderState,
((rootContainerInstance: any): Instance),
);
respondersMap.set(responder, responderInstance);
}
function updateEventListener(
listener: ReactEventResponderListener<any, any>,
fiber: Fiber,
visitedResponders: Set<ReactEventResponder<any, any>>,
respondersMap: Map<
ReactEventResponder<any, any>,
ReactEventResponderInstance<any, any>,
>,
rootContainerInstance: null | Container,
): void {
let responder;
let props;
if (listener) {
responder = listener.responder;
props = listener.props;
}
invariant(
responder && responder.$$typeof === REACT_RESPONDER_TYPE,
'An invalid value was used as an event listener. Expect one or many event ' +
'listeners created via React.unstable_useResponder().',
);
const listenerProps = ((props: any): Object);
if (visitedResponders.has(responder)) {
// show warning
if (__DEV__) {
console.error(
'Duplicate event responder "%s" found in event listeners. ' +
'Event listeners passed to elements cannot use the same event responder more than once.',
responder.displayName,
);
}
return;
}
visitedResponders.add(responder);
const responderInstance = respondersMap.get(responder);
if (responderInstance === undefined) {
// Mount (happens in either complete or commit phase)
mountEventResponder(
responder,
listenerProps,
fiber,
respondersMap,
rootContainerInstance,
);
} else {
// Update (happens during commit phase only)
responderInstance.props = listenerProps;
responderInstance.fiber = fiber;
}
}
export function updateDeprecatedEventListeners(
listeners: any,
fiber: Fiber,
rootContainerInstance: null | Container,
): void {
const visitedResponders = new Set();
let dependencies = fiber.dependencies;
if (listeners != null) {
if (dependencies === null) {
dependencies = fiber.dependencies = {
lanes: NoLanes,
firstContext: null,
responders: new Map(),
};
}
let respondersMap = dependencies.responders;
if (respondersMap === null) {
dependencies.responders = respondersMap = new Map();
}
if (isArray(listeners)) {
for (let i = 0, length = listeners.length; i < length; i++) {
const listener = listeners[i];
updateEventListener(
listener,
fiber,
visitedResponders,
respondersMap,
rootContainerInstance,
);
}
} else {
updateEventListener(
listeners,
fiber,
visitedResponders,
respondersMap,
rootContainerInstance,
);
}
}
if (dependencies !== null) {
const respondersMap = dependencies.responders;
if (respondersMap !== null) {
// Unmount
const mountedResponders = Array.from(respondersMap.keys());
for (let i = 0, length = mountedResponders.length; i < length; i++) {
const mountedResponder = mountedResponders[i];
if (!visitedResponders.has(mountedResponder)) {
const responderInstance = ((respondersMap.get(
mountedResponder,
): any): ReactEventResponderInstance<any, any>);
DEPRECATED_unmountResponderInstance(responderInstance);
respondersMap.delete(mountedResponder);
}
}
}
}
}
export function createDeprecatedResponderListener(
responder: ReactEventResponder<any, any>,
props: Object,
): ReactEventResponderListener<any, any> {
const eventResponderListener = {
responder,
props,
};
if (__DEV__) {
Object.freeze(eventResponderListener);
}
return eventResponderListener;
}
export function unmountDeprecatedResponderListeners(fiber: Fiber) {
const dependencies = fiber.dependencies;
if (dependencies !== null) {
const respondersMap = dependencies.responders;
if (respondersMap !== null) {
const responderInstances = Array.from(respondersMap.values());
for (let i = 0, length = responderInstances.length; i < length; i++) {
const responderInstance = responderInstances[i];
DEPRECATED_unmountResponderInstance(responderInstance);
}
dependencies.responders = null;
}
}
}

View File

@ -11,9 +11,7 @@ import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactEventResponder,
ReactContext,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import type {Fiber, Dispatcher} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane';
@ -46,7 +44,6 @@ import {
DefaultLanePriority,
} from './ReactFiberLane';
import {readContext} from './ReactFiberNewContext.new';
import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.new';
import {
Update as UpdateEffect,
Passive as PassiveEffect,
@ -129,7 +126,6 @@ export type HookType =
| 'useMemo'
| 'useImperativeHandle'
| 'useDebugValue'
| 'useResponder'
| 'useDeferredValue'
| 'useTransition'
| 'useMutableSource'
@ -1787,7 +1783,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useResponder: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
@ -1809,7 +1804,6 @@ const HooksDispatcherOnMount: Dispatcher = {
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
@ -1831,7 +1825,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
@ -1853,7 +1846,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
useRef: updateRef,
useState: rerenderState,
useDebugValue: updateDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: rerenderDeferredValue,
useTransition: rerenderTransition,
useMutableSource: updateMutableSource,
@ -1988,14 +1980,6 @@ if (__DEV__) {
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
mountHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
mountHookTypesDev();
@ -2120,14 +2104,6 @@ if (__DEV__) {
updateHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
@ -2252,14 +2228,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
@ -2385,14 +2353,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
@ -2528,15 +2488,6 @@ if (__DEV__) {
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
warnInvalidHookAccess();
mountHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
@ -2676,15 +2627,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
warnInvalidHookAccess();
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
@ -2825,15 +2767,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
warnInvalidHookAccess();
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();

View File

@ -11,9 +11,7 @@ import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactEventResponder,
ReactContext,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import type {Fiber, Dispatcher} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane';
@ -46,7 +44,6 @@ import {
DefaultLanePriority,
} from './ReactFiberLane';
import {readContext} from './ReactFiberNewContext.old';
import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.old';
import {
Update as UpdateEffect,
Passive as PassiveEffect,
@ -128,7 +125,6 @@ export type HookType =
| 'useMemo'
| 'useImperativeHandle'
| 'useDebugValue'
| 'useResponder'
| 'useDeferredValue'
| 'useTransition'
| 'useMutableSource'
@ -1785,7 +1781,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useResponder: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
@ -1807,7 +1802,6 @@ const HooksDispatcherOnMount: Dispatcher = {
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
@ -1829,7 +1823,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
@ -1851,7 +1844,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
useRef: updateRef,
useState: rerenderState,
useDebugValue: updateDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: rerenderDeferredValue,
useTransition: rerenderTransition,
useMutableSource: updateMutableSource,
@ -1986,14 +1978,6 @@ if (__DEV__) {
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
mountHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
mountHookTypesDev();
@ -2118,14 +2102,6 @@ if (__DEV__) {
updateHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
@ -2250,14 +2226,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
@ -2383,14 +2351,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
@ -2526,15 +2486,6 @@ if (__DEV__) {
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
warnInvalidHookAccess();
mountHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
@ -2674,15 +2625,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
warnInvalidHookAccess();
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
@ -2823,15 +2765,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props,
): ReactEventResponderListener<E, C> {
currentHookNameInDev = 'useResponder';
warnInvalidHookAccess();
updateHookTypesDev();
return createDeprecatedResponderListener(responder, props);
},
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();

View File

@ -2483,7 +2483,7 @@ function commitMutationEffectsImpl(
commitDetachRef(current);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allows us to transition away
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (fiber.tag === ScopeComponent) {
commitAttachRef(fiber);
@ -2633,7 +2633,7 @@ function commitLayoutEffectsImpl(
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allows us to transition away
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (effectTag & Ref && fiber.tag !== ScopeComponent) {
commitAttachRef(fiber);

View File

@ -2342,7 +2342,7 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
commitDetachRef(current);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allows us to transition away
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (nextEffect.tag === ScopeComponent) {
commitAttachRef(nextEffect);
@ -2429,7 +2429,7 @@ function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allows us to transition away
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (effectTag & Ref && nextEffect.tag !== ScopeComponent) {
commitAttachRef(nextEffect);

View File

@ -10,9 +10,6 @@
import type {Source} from 'shared/ReactElementType';
import type {
RefObject,
ReactEventResponder,
ReactEventResponderListener,
ReactEventResponderInstance,
ReactContext,
MutableSourceSubscribeFn,
MutableSourceGetSnapshotFn,
@ -44,10 +41,6 @@ export type ContextDependency<T> = {
export type Dependencies = {
lanes: Lanes,
firstContext: ContextDependency<mixed> | null,
responders: Map<
ReactEventResponder<any, any>,
ReactEventResponderInstance<any, any>,
> | null,
...
};
@ -298,10 +291,6 @@ export type Dispatcher = {|
deps: Array<mixed> | void | null,
): void,
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props: Object,
): ReactEventResponderListener<E, C>,
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T,
useTransition(
config: SuspenseConfig | void | null,

View File

@ -19,7 +19,6 @@ describe('ReactScope', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableScopeAPI = true;
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
Scheduler = require('scheduler');
});

View File

@ -61,10 +61,6 @@ export const warnsIfNotActing = $$$hostConfig.warnsIfNotActing;
export const supportsMutation = $$$hostConfig.supportsMutation;
export const supportsPersistence = $$$hostConfig.supportsPersistence;
export const supportsHydration = $$$hostConfig.supportsHydration;
export const DEPRECATED_mountResponderInstance =
$$$hostConfig.DEPRECATED_mountResponderInstance;
export const DEPRECATED_unmountResponderInstance =
$$$hostConfig.DEPRECATED_unmountResponderInstance;
export const getFundamentalComponentInstance =
$$$hostConfig.getFundamentalComponentInstance;
export const mountFundamentalComponent =

View File

@ -7,13 +7,8 @@
* @flow
*/
import type {
ReactEventResponder,
ReactEventResponderInstance,
ReactFundamentalComponentInstance,
} from 'shared/ReactTypes';
import type {ReactFundamentalComponentInstance} from 'shared/ReactTypes';
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
export type Type = string;
@ -58,7 +53,6 @@ export * from 'react-reconciler/src/ReactFiberHostConfigWithNoPersistence';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors';
const EVENT_COMPONENT_CONTEXT = {};
const NO_CONTEXT = {};
const UPDATE_SIGNAL = {};
const nodeToInstanceMap = new WeakMap();
@ -161,19 +155,9 @@ export function createInstance(
hostContext: Object,
internalInstanceHandle: Object,
): Instance {
let propsToUse = props;
if (enableDeprecatedFlareAPI) {
if (props.DEPRECATED_flareListeners != null) {
// We want to remove the "DEPRECATED_flareListeners" prop
// as we don't want it in the test renderer's
// instance props.
const {DEPRECATED_flareListeners, ...otherProps} = props; // eslint-disable-line
propsToUse = otherProps;
}
}
return {
type,
props: propsToUse,
props,
isHidden: false,
children: [],
internalInstanceHandle,
@ -224,17 +208,6 @@ export function createTextInstance(
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
if (__DEV__) {
if (enableDeprecatedFlareAPI) {
if (hostContext === EVENT_COMPONENT_CONTEXT) {
console.error(
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
'Wrap the child text "%s" in an element.',
text,
);
}
}
}
return {
text,
isHidden: false,
@ -311,22 +284,6 @@ export function unhideTextInstance(
textInstance.isHidden = false;
}
export function DEPRECATED_mountResponderInstance(
responder: ReactEventResponder<any, any>,
responderInstance: ReactEventResponderInstance<any, any>,
props: Object,
state: Object,
instance: Instance,
) {
// noop
}
export function DEPRECATED_unmountResponderInstance(
responderInstance: ReactEventResponderInstance<any, any>,
): void {
// noop
}
export function getFundamentalComponentInstance(
fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
): Instance {

View File

@ -52,9 +52,6 @@ export {
// enableBlocksAPI
block,
block as unstable_block,
// enableDeprecatedFlareAPI
DEPRECATED_useResponder,
DEPRECATED_createResponder,
// enableScopeAPI
unstable_Scope,
unstable_useOpaqueIdentifier,

View File

@ -80,8 +80,6 @@ export {
block,
block as unstable_block,
unstable_LegacyHidden,
DEPRECATED_useResponder,
DEPRECATED_createResponder,
unstable_createFundamental,
unstable_Scope,
unstable_useOpaqueIdentifier,

View File

@ -51,9 +51,6 @@ export {
// enableBlocksAPI
block,
block as unstable_block,
// enableDeprecatedFlareAPI
DEPRECATED_useResponder,
DEPRECATED_createResponder,
// enableScopeAPI
unstable_Scope,
unstable_useOpaqueIdentifier,

View File

@ -45,7 +45,6 @@ import {
useReducer,
useRef,
useState,
useResponder,
useTransition,
useDeferredValue,
useOpaqueIdentifier,
@ -59,7 +58,6 @@ import {
import {createMutableSource} from './ReactMutableSource';
import ReactSharedInternals from './ReactSharedInternals';
import {createFundamental} from './ReactFundamental';
import {createEventResponder} from './ReactEventResponder';
// TODO: Move this branching into the other module instead and just re-export.
const createElement = __DEV__ ? createElementWithValidation : createElementProd;
@ -115,9 +113,6 @@ export {
withSuspenseConfig as unstable_withSuspenseConfig,
// enableBlocksAPI
block,
// enableDeprecatedFlareAPI
useResponder as DEPRECATED_useResponder,
createEventResponder as DEPRECATED_createResponder,
// enableFundamentalAPI
createFundamental as unstable_createFundamental,
// enableScopeAPI

View File

@ -1,46 +0,0 @@
/**
* 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 {ReactEventResponder} from 'shared/ReactTypes';
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
import {hasBadMapPolyfill} from './BadMapPolyfill';
export function createEventResponder<E, C>(
displayName: string,
responderConfig: Object,
): ReactEventResponder<E, C> {
const {
getInitialState,
onEvent,
onMount,
onUnmount,
onRootEvent,
rootEventTypes,
targetEventTypes,
targetPortalPropagation,
} = responderConfig;
const eventResponder = {
$$typeof: REACT_RESPONDER_TYPE,
displayName,
getInitialState: getInitialState || null,
onEvent: onEvent || null,
onMount: onMount || null,
onRootEvent: onRootEvent || null,
onUnmount: onUnmount || null,
rootEventTypes: rootEventTypes || null,
targetEventTypes: targetEventTypes || null,
targetPortalPropagation: targetPortalPropagation || false,
};
// We use responder as a Map key later on. When we have a bad
// polyfill, then we can't use it as a key as the polyfill tries
// to add a property to the object.
if (__DEV__ && !hasBadMapPolyfill) {
Object.freeze(eventResponder);
}
return eventResponder;
}

View File

@ -12,13 +12,10 @@ import type {
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
ReactEventResponder,
ReactEventResponderListener,
} from 'shared/ReactTypes';
import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig';
import invariant from 'shared/invariant';
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
@ -154,23 +151,6 @@ export function useDebugValue<T>(
export const emptyObject = {};
export function useResponder(
responder: ReactEventResponder<any, any>,
listenerProps: ?Object,
): ?ReactEventResponderListener<any, any> {
const dispatcher = resolveDispatcher();
if (__DEV__) {
if (responder == null || responder.$$typeof !== REACT_RESPONDER_TYPE) {
console.error(
'useResponder: invalid first argument. Expected an event responder, but instead got %s',
responder,
);
return;
}
}
return dispatcher.useResponder(responder, listenerProps || emptyObject);
}
export function useTransition(
config: ?Object,
): [(() => void) => void, boolean] {

View File

@ -54,9 +54,6 @@ export const enableSchedulerDebugging = false;
// Disable javascript: URL strings in href for XSS protection.
export const disableJavaScriptURLs = false;
// Experimental React Flare event system and event components support.
export const enableDeprecatedFlareAPI = false;
// Experimental Host Component support.
export const enableFundamentalAPI = false;

View File

@ -28,7 +28,6 @@ export let REACT_LAZY_TYPE = 0xead4;
export let REACT_BLOCK_TYPE = 0xead9;
export let REACT_SERVER_BLOCK_TYPE = 0xeada;
export let REACT_FUNDAMENTAL_TYPE = 0xead5;
export let REACT_RESPONDER_TYPE = 0xead6;
export let REACT_SCOPE_TYPE = 0xead7;
export let REACT_OPAQUE_ID_TYPE = 0xeae0;
export let REACT_DEBUG_TRACING_MODE_TYPE = 0xeae1;
@ -52,7 +51,6 @@ if (typeof Symbol === 'function' && Symbol.for) {
REACT_BLOCK_TYPE = symbolFor('react.block');
REACT_SERVER_BLOCK_TYPE = symbolFor('react.server.block');
REACT_FUNDAMENTAL_TYPE = symbolFor('react.fundamental');
REACT_RESPONDER_TYPE = symbolFor('react.responder');
REACT_SCOPE_TYPE = symbolFor('react.scope');
REACT_OPAQUE_ID_TYPE = symbolFor('react.opaque.id');
REACT_DEBUG_TRACING_MODE_TYPE = symbolFor('react.debug_trace_mode');

View File

@ -86,37 +86,6 @@ export type RefObject = {|
current: any,
|};
export type ReactEventResponderInstance<E, C> = {|
fiber: Object,
props: Object,
responder: ReactEventResponder<E, C>,
rootEventTypes: null | Set<string>,
state: Object,
|};
export type ReactEventResponderListener<E, C> = {|
props: Object,
responder: ReactEventResponder<E, C>,
|};
export type ReactEventResponder<E, C> = {
$$typeof: Symbol | number,
displayName: string,
targetEventTypes: null | Array<string>,
targetPortalPropagation: boolean,
rootEventTypes: null | Array<string>,
getInitialState: null | ((props: Object) => Object),
onEvent:
| null
| ((event: E, context: C, props: Object, state: Object) => void),
onRootEvent:
| null
| ((event: E, context: C, props: Object, state: Object) => void),
onMount: null | ((context: C, props: Object, state: Object) => void),
onUnmount: null | ((context: C, props: Object, state: Object) => void),
...
};
export type EventPriority = 0 | 1 | 2;
export const DiscreteEvent: EventPriority = 0;

View File

@ -27,7 +27,6 @@ export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = true;
export const enableDeprecatedFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;

View File

@ -26,7 +26,6 @@ export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableDeprecatedFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;

View File

@ -26,7 +26,6 @@ export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableDeprecatedFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;

View File

@ -26,7 +26,6 @@ export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableDeprecatedFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;

View File

@ -26,7 +26,6 @@ export const enableLazyElements = false;
export const enableSchedulerDebugging = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableDeprecatedFlareAPI = true;
export const enableFundamentalAPI = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = false;

View File

@ -26,7 +26,6 @@ export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableDeprecatedFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;

View File

@ -26,7 +26,6 @@ export const enableLazyElements = false;
export const disableJavaScriptURLs = true;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableDeprecatedFlareAPI = true;
export const enableFundamentalAPI = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = true;

View File

@ -60,8 +60,6 @@ export const disableJavaScriptURLs = true;
export const disableModulePatternComponents = true;
export const enableDeprecatedFlareAPI = true;
export const enableCreateEventHandleAPI = true;
export const enableFundamentalAPI = false;

View File

@ -20,7 +20,6 @@ import {
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
REACT_FUNDAMENTAL_TYPE,
REACT_RESPONDER_TYPE,
REACT_SCOPE_TYPE,
REACT_BLOCK_TYPE,
REACT_SERVER_BLOCK_TYPE,
@ -55,7 +54,6 @@ export default function isValidElementType(type: mixed) {
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
type.$$typeof === REACT_FUNDAMENTAL_TYPE ||
type.$$typeof === REACT_RESPONDER_TYPE ||
type.$$typeof === REACT_BLOCK_TYPE ||
type[(0: any)] === REACT_SERVER_BLOCK_TYPE
) {

View File

@ -322,8 +322,6 @@
"321": "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.",
"322": "forwardRef requires a render function but was given %s.",
"323": "React has blocked a javascript: URL as a security precaution.",
"324": "An event responder context was used outside of an event cycle. Use context.setTimeout() to use asynchronous responder context outside of event cycle .",
"325": "addRootEventTypes() found a duplicate root event type of \"%s\". This might be because the event type exists in the event responder \"rootEventTypes\" array or because of a previous addRootEventTypes() using this root event type.",
"326": "Expected a valid priority level",
"327": "Should not already be working.",
"328": "Should have a work-in-progress.",
@ -334,17 +332,13 @@
"333": "This should have a parent host component initialized. This error is likely caused by a bug in React. Please file an issue.",
"334": "accumulate(...): Accumulated items must not be null or undefined.",
"335": "ReactDOMServer does not yet support the event API.",
"336": "The \"%s\" event responder cannot be used via the \"useEvent\" hook.",
"337": "An invalid event responder was provided to host component",
"338": "ReactDOMServer does not yet support the fundamental API.",
"339": "An invalid value was used as an event listener. Expect one or many event listeners created via React.unstable_useResponder().",
"340": "Threw in newly mounted dehydrated component. This is likely a bug in React. Please file an issue.",
"341": "We just came from a parent so we must have had a parent. This is a bug in React.",
"342": "A React component suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.",
"343": "ReactDOMServer does not yet support scope components.",
"344": "Expected prepareToHydrateHostSuspenseInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
"345": "Root did not complete. This is a bug in React.",
"346": "An event responder context was used outside of an event cycle.",
"348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React.",
"349": "Expected a work-in-progress root. This is a bug in React. Please file an issue.",
"350": "Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.",

View File

@ -16,7 +16,6 @@ jest.mock('shared/ReactFeatureFlags', () => {
wwwFlags.disableLegacyContext = defaultFlags.disableLegacyContext;
wwwFlags.warnAboutUnmockedScheduler = defaultFlags.warnAboutUnmockedScheduler;
wwwFlags.disableJavaScriptURLs = defaultFlags.disableJavaScriptURLs;
wwwFlags.enableDeprecatedFlareAPI = defaultFlags.enableDeprecatedFlareAPI;
return wwwFlags;
});

View File

@ -370,8 +370,6 @@ function getPlugins(
useForks(forks),
// Ensure we don't try to bundle any fbjs modules.
forbidFBJSImports(),
// Replace any externals with their valid internal FB mappings
isFBWWWBundle && replace(Bundles.fbBundleExternalsMap),
// Use Node resolution mechanism.
resolve({
skip: externals,
@ -564,10 +562,6 @@ async function createBundle(bundle, bundleType) {
const deps = Modules.getDependencies(bundleType, bundle.entry);
externals = externals.concat(deps);
}
if (isFBWWWBundle) {
// Add any mapped fb bundle externals
externals = externals.concat(Object.values(Bundles.fbBundleExternalsMap));
}
const importSideEffects = Modules.getImportSideEffects();
const pureExternalModules = Object.keys(importSideEffects).filter(

View File

@ -704,75 +704,8 @@ const bundles = [
global: 'SchedulerTracing',
externals: [],
},
/******* React Events (experimental) *******/
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-interactions/events/context-menu',
global: 'ReactEventsContextMenu',
externals: ['react'],
},
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-interactions/events/deprecated-focus',
global: 'ReactEventsFocus',
externals: ['react'],
},
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-interactions/events/hover',
global: 'ReactEventsHover',
externals: ['react'],
},
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-interactions/events/press-legacy',
global: 'ReactEventsPressLegacy',
externals: ['react'],
},
];
const fbBundleExternalsMap = {
'react-interactions/events/focus': 'ReactEventsFocus',
'react-interactions/events/tap': 'ReactEventsTap',
};
// Based on deep-freeze by substack (public domain)
function deepFreeze(o) {
Object.freeze(o);
@ -829,7 +762,6 @@ function getFilename(bundle, bundleType) {
}
module.exports = {
fbBundleExternalsMap,
bundleTypes,
moduleTypes,
bundles,