Remove the deprecated React Flare event system (#19520)
This commit is contained in:
parent
8d57ca519a
commit
b61174fb7b
|
@ -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.');
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -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)';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
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() {
|
||||
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() {
|
||||
|
@ -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() {
|
||||
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() {
|
||||
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,8 +232,6 @@ 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,
|
||||
|
@ -248,26 +240,6 @@ export function dispatchEvent(
|
|||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt dispatching an event. Returns a SuspenseInstance or Container if it's blocked.
|
||||
export function attemptToDispatchEvent(
|
||||
|
@ -318,9 +290,6 @@ export function attemptToDispatchEvent(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enableDeprecatedFlareAPI) {
|
||||
if (eventSystemFlags & PLUGIN_EVENT_SYSTEM) {
|
||||
dispatchEventForPluginEventSystem(
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
|
@ -328,26 +297,6 @@ export function attemptToDispatchEvent(
|
|||
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,
|
||||
);
|
||||
}
|
||||
// We're not blocked on anything.
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`.
|
|
@ -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';
|
|
@ -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';
|
|
@ -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).
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
|
@ -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';
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -64,7 +64,7 @@ export function discreteUpdates(fn, a, b, c, d) {
|
|||
}
|
||||
}
|
||||
|
||||
export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
|
||||
export function flushDiscreteUpdatesIfNeeded() {
|
||||
if (!isInsideEventHandler) {
|
||||
flushDiscreteUpdatesImpl();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,38 +1257,15 @@ 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 (current.ref !== workInProgress.ref) {
|
||||
markRef(workInProgress);
|
||||
}
|
||||
|
|
|
@ -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,38 +1230,15 @@ 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 (current.ref !== workInProgress.ref) {
|
||||
markRef(workInProgress);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -19,7 +19,6 @@ describe('ReactScope', () => {
|
|||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableScopeAPI = true;
|
||||
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
|
||||
React = require('react');
|
||||
Scheduler = require('scheduler');
|
||||
});
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -52,9 +52,6 @@ export {
|
|||
// enableBlocksAPI
|
||||
block,
|
||||
block as unstable_block,
|
||||
// enableDeprecatedFlareAPI
|
||||
DEPRECATED_useResponder,
|
||||
DEPRECATED_createResponder,
|
||||
// enableScopeAPI
|
||||
unstable_Scope,
|
||||
unstable_useOpaqueIdentifier,
|
||||
|
|
|
@ -80,8 +80,6 @@ export {
|
|||
block,
|
||||
block as unstable_block,
|
||||
unstable_LegacyHidden,
|
||||
DEPRECATED_useResponder,
|
||||
DEPRECATED_createResponder,
|
||||
unstable_createFundamental,
|
||||
unstable_Scope,
|
||||
unstable_useOpaqueIdentifier,
|
||||
|
|
|
@ -51,9 +51,6 @@ export {
|
|||
// enableBlocksAPI
|
||||
block,
|
||||
block as unstable_block,
|
||||
// enableDeprecatedFlareAPI
|
||||
DEPRECATED_useResponder,
|
||||
DEPRECATED_createResponder,
|
||||
// enableScopeAPI
|
||||
unstable_Scope,
|
||||
unstable_useOpaqueIdentifier,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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] {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -60,8 +60,6 @@ export const disableJavaScriptURLs = true;
|
|||
|
||||
export const disableModulePatternComponents = true;
|
||||
|
||||
export const enableDeprecatedFlareAPI = true;
|
||||
|
||||
export const enableCreateEventHandleAPI = true;
|
||||
|
||||
export const enableFundamentalAPI = false;
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue