mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Experimental event API: refactor responder modules for lifecycle inclusion (#15322)
This commit is contained in:
@@ -10,34 +10,40 @@
|
||||
import type {AnyNativeEvent} from 'events/PluginModuleType';
|
||||
import type {ReactEventResponderEventType} from 'shared/ReactTypes';
|
||||
|
||||
export type EventResponderContext = {
|
||||
event: AnyNativeEvent,
|
||||
eventTarget: Element | Document,
|
||||
eventType: string,
|
||||
isPassive: () => boolean,
|
||||
isPassiveSupported: () => boolean,
|
||||
dispatchEvent: <E>(
|
||||
eventObject: E,
|
||||
{
|
||||
capture?: boolean,
|
||||
discrete?: boolean,
|
||||
stopPropagation?: boolean,
|
||||
},
|
||||
export type ResponderEvent = {
|
||||
nativeEvent: AnyNativeEvent,
|
||||
target: Element | Document,
|
||||
type: string,
|
||||
passive: boolean,
|
||||
passiveSupported: boolean,
|
||||
};
|
||||
|
||||
export type ResponderDispatchEventOptions = {
|
||||
capture?: boolean,
|
||||
discrete?: boolean,
|
||||
stopPropagation?: boolean,
|
||||
};
|
||||
|
||||
export type ResponderContext = {
|
||||
dispatchEvent: (
|
||||
eventObject: Object,
|
||||
otpions: ResponderDispatchEventOptions,
|
||||
) => void,
|
||||
isTargetWithinElement: (
|
||||
childTarget: Element | Document,
|
||||
parentTarget: Element | Document,
|
||||
) => boolean,
|
||||
isTargetOwned: (Element | Document) => boolean,
|
||||
isTargetWithinEventComponent: (Element | Document) => boolean,
|
||||
isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
|
||||
addRootEventTypes: (
|
||||
document: Document,
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
) => void,
|
||||
removeRootEventTypes: (
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
) => void,
|
||||
requestOwnership: (target: Element | Document | null) => boolean,
|
||||
releaseOwnership: (target: Element | Document | null) => boolean,
|
||||
withAsyncDispatching: (func: () => void) => void,
|
||||
hasOwnership: () => boolean,
|
||||
requestOwnership: () => boolean,
|
||||
releaseOwnership: () => boolean,
|
||||
setTimeout: (func: () => void, timeout: number) => TimeoutID,
|
||||
};
|
||||
|
||||
+225
-246
@@ -6,6 +6,11 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
ResponderContext,
|
||||
ResponderEvent,
|
||||
ResponderDispatchEventOptions,
|
||||
} from 'events/EventTypes';
|
||||
import {
|
||||
type EventSystemFlags,
|
||||
IS_PASSIVE,
|
||||
@@ -20,13 +25,13 @@ import type {
|
||||
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
|
||||
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
|
||||
|
||||
import warning from 'shared/warning';
|
||||
import {enableEventAPI} from 'shared/ReactFeatureFlags';
|
||||
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
let listenToResponderEventTypesImpl;
|
||||
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
|
||||
|
||||
export let listenToResponderEventTypesImpl;
|
||||
|
||||
export function setListenToResponderEventTypes(
|
||||
_listenToResponderEventTypesImpl: Function,
|
||||
@@ -34,32 +39,216 @@ export function setListenToResponderEventTypes(
|
||||
listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl;
|
||||
}
|
||||
|
||||
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
|
||||
|
||||
const rootEventTypesToEventComponents: Map<
|
||||
DOMTopLevelEventType | string,
|
||||
Set<Fiber>,
|
||||
> = new Map();
|
||||
const targetEventTypeCached: Map<
|
||||
Array<ReactEventResponderEventType>,
|
||||
Set<DOMTopLevelEventType>,
|
||||
> = new Map();
|
||||
const targetOwnership: Map<EventTarget, Fiber> = new Map();
|
||||
const eventsWithStopPropagation:
|
||||
| WeakSet
|
||||
| Set<$Shape<PartialEventObject>> = new PossiblyWeakSet();
|
||||
type EventQueue = {
|
||||
bubble: null | Array<$Shape<PartialEventObject>>,
|
||||
capture: null | Array<$Shape<PartialEventObject>>,
|
||||
discrete: boolean,
|
||||
};
|
||||
|
||||
type PartialEventObject = {
|
||||
listener: ($Shape<PartialEventObject>) => void,
|
||||
target: Element | Document,
|
||||
type: string,
|
||||
};
|
||||
type EventQueue = {
|
||||
bubble: null | Array<$Shape<PartialEventObject>>,
|
||||
capture: null | Array<$Shape<PartialEventObject>>,
|
||||
discrete: boolean,
|
||||
|
||||
let currentOwner = null;
|
||||
let currentFiber: Fiber;
|
||||
let currentResponder: ReactEventResponder;
|
||||
let currentEventQueue: EventQueue;
|
||||
|
||||
const eventResponderContext: ResponderContext = {
|
||||
dispatchEvent(
|
||||
possibleEventObject: Object,
|
||||
{capture, discrete, stopPropagation}: ResponderDispatchEventOptions,
|
||||
): void {
|
||||
const eventQueue = currentEventQueue;
|
||||
const {listener, target, type} = possibleEventObject;
|
||||
|
||||
if (listener == null || target == null || type == null) {
|
||||
throw new Error(
|
||||
'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
possibleEventObject.preventDefault = () => {
|
||||
// Update this warning when we have a story around dealing with preventDefault
|
||||
warning(
|
||||
false,
|
||||
'preventDefault() is no longer available on event objects created from event responder modules.',
|
||||
);
|
||||
};
|
||||
possibleEventObject.stopPropagation = () => {
|
||||
// Update this warning when we have a story around dealing with stopPropgation
|
||||
warning(
|
||||
false,
|
||||
'stopPropagation() is no longer available on event objects created from event responder modules.',
|
||||
);
|
||||
};
|
||||
}
|
||||
const eventObject = ((possibleEventObject: any): $Shape<
|
||||
PartialEventObject,
|
||||
>);
|
||||
let events;
|
||||
|
||||
if (capture) {
|
||||
events = eventQueue.capture;
|
||||
if (events === null) {
|
||||
events = eventQueue.capture = [];
|
||||
}
|
||||
} else {
|
||||
events = eventQueue.bubble;
|
||||
if (events === null) {
|
||||
events = eventQueue.bubble = [];
|
||||
}
|
||||
}
|
||||
if (discrete) {
|
||||
eventQueue.discrete = true;
|
||||
}
|
||||
events.push(eventObject);
|
||||
|
||||
if (stopPropagation) {
|
||||
eventsWithStopPropagation.add(eventObject);
|
||||
}
|
||||
},
|
||||
isPositionWithinTouchHitTarget(x: number, y: number): boolean {
|
||||
return false;
|
||||
},
|
||||
isTargetWithinEventComponent(target: Element | Document): boolean {
|
||||
const eventFiber = currentFiber;
|
||||
|
||||
if (target != null) {
|
||||
let fiber = getClosestInstanceFromNode(target);
|
||||
while (fiber !== null) {
|
||||
if (fiber === eventFiber || fiber === eventFiber.alternate) {
|
||||
return true;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isTargetWithinElement(
|
||||
childTarget: Element | Document,
|
||||
parentTarget: Element | Document,
|
||||
): boolean {
|
||||
const childFiber = getClosestInstanceFromNode(childTarget);
|
||||
const parentFiber = getClosestInstanceFromNode(parentTarget);
|
||||
|
||||
let node = childFiber;
|
||||
while (node !== null) {
|
||||
if (node === parentFiber) {
|
||||
return true;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
addRootEventTypes(
|
||||
doc: Document,
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
): void {
|
||||
listenToResponderEventTypesImpl(rootEventTypes, doc);
|
||||
const eventComponent = currentFiber;
|
||||
for (let i = 0; i < rootEventTypes.length; i++) {
|
||||
const rootEventType = rootEventTypes[i];
|
||||
const topLevelEventType =
|
||||
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
|
||||
let rootEventComponents = rootEventTypesToEventComponents.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
if (rootEventComponents === undefined) {
|
||||
rootEventComponents = new Set();
|
||||
rootEventTypesToEventComponents.set(
|
||||
topLevelEventType,
|
||||
rootEventComponents,
|
||||
);
|
||||
}
|
||||
rootEventComponents.add(eventComponent);
|
||||
}
|
||||
},
|
||||
removeRootEventTypes(
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
): void {
|
||||
const eventComponent = currentFiber;
|
||||
for (let i = 0; i < rootEventTypes.length; i++) {
|
||||
const rootEventType = rootEventTypes[i];
|
||||
const topLevelEventType =
|
||||
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
|
||||
let rootEventComponents = rootEventTypesToEventComponents.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
if (rootEventComponents !== undefined) {
|
||||
rootEventComponents.delete(eventComponent);
|
||||
}
|
||||
}
|
||||
},
|
||||
hasOwnership(): boolean {
|
||||
return currentOwner === currentFiber;
|
||||
},
|
||||
requestOwnership(): boolean {
|
||||
if (currentOwner !== null) {
|
||||
return false;
|
||||
}
|
||||
currentOwner = currentFiber;
|
||||
return true;
|
||||
},
|
||||
releaseOwnership(): boolean {
|
||||
if (currentOwner !== currentFiber) {
|
||||
return false;
|
||||
}
|
||||
currentOwner = null;
|
||||
return false;
|
||||
},
|
||||
setTimeout(func: () => void, delay): TimeoutID {
|
||||
const contextResponder = currentResponder;
|
||||
const contextFiber = currentFiber;
|
||||
return setTimeout(() => {
|
||||
const previousEventQueue = currentEventQueue;
|
||||
const previousFiber = currentFiber;
|
||||
const previousResponder = currentResponder;
|
||||
currentEventQueue = createEventQueue();
|
||||
currentResponder = contextResponder;
|
||||
currentFiber = contextFiber;
|
||||
try {
|
||||
func();
|
||||
batchedUpdates(processEventQueue, currentEventQueue);
|
||||
} finally {
|
||||
currentFiber = previousFiber;
|
||||
currentEventQueue = previousEventQueue;
|
||||
currentResponder = previousResponder;
|
||||
}
|
||||
}, delay);
|
||||
},
|
||||
};
|
||||
|
||||
const rootEventTypesToEventComponents: Map<
|
||||
DOMTopLevelEventType | string,
|
||||
Set<Fiber>,
|
||||
> = new Map();
|
||||
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
|
||||
const eventsWithStopPropagation:
|
||||
| WeakSet
|
||||
| Set<$Shape<PartialEventObject>> = new PossiblyWeakSet();
|
||||
const targetEventTypeCached: Map<
|
||||
Array<ReactEventResponderEventType>,
|
||||
Set<DOMTopLevelEventType>,
|
||||
> = new Map();
|
||||
|
||||
function createResponderEvent(
|
||||
topLevelType: string,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: Element | Document,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
): ResponderEvent {
|
||||
return {
|
||||
nativeEvent: nativeEvent,
|
||||
target: nativeEventTarget,
|
||||
type: topLevelType,
|
||||
passive: (eventSystemFlags & IS_PASSIVE) !== 0,
|
||||
passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0,
|
||||
};
|
||||
}
|
||||
|
||||
function createEventQueue(): EventQueue {
|
||||
return {
|
||||
bubble: null,
|
||||
@@ -100,8 +289,8 @@ function processEvents(
|
||||
}
|
||||
}
|
||||
|
||||
function processEventQueue(eventQueue: EventQueue): void {
|
||||
const {bubble, capture, discrete} = eventQueue;
|
||||
export function processEventQueue(): void {
|
||||
const {bubble, capture, discrete} = currentEventQueue;
|
||||
|
||||
if (discrete) {
|
||||
interactiveUpdates(() => {
|
||||
@@ -112,218 +301,6 @@ function processEventQueue(eventQueue: EventQueue): void {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add context methods for dispatching events
|
||||
function DOMEventResponderContext(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
) {
|
||||
this.event = nativeEvent;
|
||||
this.eventTarget = nativeEventTarget;
|
||||
this.eventType = topLevelType;
|
||||
this._flags = eventSystemFlags;
|
||||
this._fiber = null;
|
||||
this._responder = null;
|
||||
this._discreteEvents = null;
|
||||
this._nonDiscreteEvents = null;
|
||||
this._isBatching = true;
|
||||
this._eventQueue = createEventQueue();
|
||||
}
|
||||
|
||||
DOMEventResponderContext.prototype.isPassive = function(): boolean {
|
||||
return (this._flags & IS_PASSIVE) !== 0;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean {
|
||||
return (this._flags & PASSIVE_NOT_SUPPORTED) === 0;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.dispatchEvent = function(
|
||||
possibleEventObject: Object,
|
||||
{
|
||||
capture,
|
||||
discrete,
|
||||
stopPropagation,
|
||||
}: {
|
||||
capture?: boolean,
|
||||
discrete?: boolean,
|
||||
stopPropagation?: boolean,
|
||||
},
|
||||
): void {
|
||||
const eventQueue = this._eventQueue;
|
||||
const {listener, target, type} = possibleEventObject;
|
||||
|
||||
if (listener == null || target == null || type == null) {
|
||||
throw new Error(
|
||||
'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
possibleEventObject.preventDefault = () => {
|
||||
// Update this warning when we have a story around dealing with preventDefault
|
||||
warning(
|
||||
false,
|
||||
'preventDefault() is no longer available on event objects created from event responder modules.',
|
||||
);
|
||||
};
|
||||
possibleEventObject.stopPropagation = () => {
|
||||
// Update this warning when we have a story around dealing with stopPropgation
|
||||
warning(
|
||||
false,
|
||||
'stopPropagation() is no longer available on event objects created from event responder modules.',
|
||||
);
|
||||
};
|
||||
}
|
||||
const eventObject = ((possibleEventObject: any): $Shape<PartialEventObject>);
|
||||
let events;
|
||||
|
||||
if (capture) {
|
||||
events = eventQueue.capture;
|
||||
if (events === null) {
|
||||
events = eventQueue.capture = [];
|
||||
}
|
||||
} else {
|
||||
events = eventQueue.bubble;
|
||||
if (events === null) {
|
||||
events = eventQueue.bubble = [];
|
||||
}
|
||||
}
|
||||
if (discrete) {
|
||||
eventQueue.discrete = true;
|
||||
}
|
||||
events.push(eventObject);
|
||||
|
||||
if (stopPropagation) {
|
||||
eventsWithStopPropagation.add(eventObject);
|
||||
}
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isTargetWithinEventComponent = function(
|
||||
target: AnyNativeEvent,
|
||||
): boolean {
|
||||
const eventFiber = this._fiber;
|
||||
|
||||
if (target != null) {
|
||||
let fiber = getClosestInstanceFromNode(target);
|
||||
while (fiber !== null) {
|
||||
if (fiber === eventFiber || fiber === eventFiber.alternate) {
|
||||
return true;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isTargetWithinElement = function(
|
||||
childTarget: EventTarget,
|
||||
parentTarget: EventTarget,
|
||||
): boolean {
|
||||
const childFiber = getClosestInstanceFromNode(childTarget);
|
||||
const parentFiber = getClosestInstanceFromNode(parentTarget);
|
||||
|
||||
let currentFiber = childFiber;
|
||||
while (currentFiber !== null) {
|
||||
if (currentFiber === parentFiber) {
|
||||
return true;
|
||||
}
|
||||
currentFiber = currentFiber.return;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.addRootEventTypes = function(
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
) {
|
||||
const element = this.eventTarget.ownerDocument;
|
||||
listenToResponderEventTypesImpl(rootEventTypes, element);
|
||||
const eventComponent = this._fiber;
|
||||
for (let i = 0; i < rootEventTypes.length; i++) {
|
||||
const rootEventType = rootEventTypes[i];
|
||||
const topLevelEventType =
|
||||
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
|
||||
let rootEventComponents = rootEventTypesToEventComponents.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
if (rootEventComponents === undefined) {
|
||||
rootEventComponents = new Set();
|
||||
rootEventTypesToEventComponents.set(
|
||||
topLevelEventType,
|
||||
rootEventComponents,
|
||||
);
|
||||
}
|
||||
rootEventComponents.add(eventComponent);
|
||||
}
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.removeRootEventTypes = function(
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
): void {
|
||||
const eventComponent = this._fiber;
|
||||
for (let i = 0; i < rootEventTypes.length; i++) {
|
||||
const rootEventType = rootEventTypes[i];
|
||||
const topLevelEventType =
|
||||
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
|
||||
let rootEventComponents = rootEventTypesToEventComponents.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
if (rootEventComponents !== undefined) {
|
||||
rootEventComponents.delete(eventComponent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() {
|
||||
// TODO
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.isTargetOwned = function(
|
||||
targetElement: Element | Node,
|
||||
): boolean {
|
||||
const targetDoc = targetElement.ownerDocument;
|
||||
return targetOwnership.has(targetDoc);
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.requestOwnership = function(
|
||||
targetElement: Element | Node,
|
||||
): boolean {
|
||||
const targetDoc = targetElement.ownerDocument;
|
||||
if (targetOwnership.has(targetDoc)) {
|
||||
return false;
|
||||
}
|
||||
targetOwnership.set(targetDoc, this._fiber);
|
||||
return true;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.releaseOwnership = function(
|
||||
targetElement: Element | Node,
|
||||
): boolean {
|
||||
const targetDoc = targetElement.ownerDocument;
|
||||
if (!targetOwnership.has(targetDoc)) {
|
||||
return false;
|
||||
}
|
||||
const owner = targetOwnership.get(targetDoc);
|
||||
if (owner === this._fiber || owner === this._fiber.alternate) {
|
||||
targetOwnership.delete(targetDoc);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
DOMEventResponderContext.prototype.withAsyncDispatching = function(
|
||||
func: () => void,
|
||||
) {
|
||||
const previousEventQueue = this._eventQueue;
|
||||
this._eventQueue = createEventQueue();
|
||||
try {
|
||||
func();
|
||||
batchedUpdates(processEventQueue, this._eventQueue);
|
||||
} finally {
|
||||
this._eventQueue = previousEventQueue;
|
||||
}
|
||||
};
|
||||
|
||||
function getTargetEventTypes(
|
||||
eventTypes: Array<ReactEventResponderEventType>,
|
||||
): Set<DOMTopLevelEventType> {
|
||||
@@ -345,7 +322,7 @@ function getTargetEventTypes(
|
||||
function handleTopLevelType(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
responderEvent: ResponderEvent,
|
||||
isRootLevelEvent: boolean,
|
||||
): void {
|
||||
const responder: ReactEventResponder = fiber.type.responder;
|
||||
@@ -360,9 +337,10 @@ function handleTopLevelType(
|
||||
if (state === null && responder.createInitialState !== undefined) {
|
||||
state = fiber.stateNode.state = responder.createInitialState(props);
|
||||
}
|
||||
context._fiber = fiber;
|
||||
context._responder = responder;
|
||||
responder.handleEvent(context, props, state);
|
||||
currentFiber = fiber;
|
||||
currentResponder = responder;
|
||||
|
||||
responder.onEvent(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
|
||||
export function runResponderEventsInBatch(
|
||||
@@ -373,17 +351,18 @@ export function runResponderEventsInBatch(
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
): void {
|
||||
if (enableEventAPI) {
|
||||
const context = new DOMEventResponderContext(
|
||||
topLevelType,
|
||||
currentEventQueue = createEventQueue();
|
||||
const responderEvent = createResponderEvent(
|
||||
((topLevelType: any): string),
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
((nativeEventTarget: any): Element | Document),
|
||||
eventSystemFlags,
|
||||
);
|
||||
let node = targetFiber;
|
||||
// Traverse up the fiber tree till we find event component fibers.
|
||||
while (node !== null) {
|
||||
if (node.tag === EventComponent) {
|
||||
handleTopLevelType(topLevelType, node, context, false);
|
||||
handleTopLevelType(topLevelType, node, responderEvent, false);
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
@@ -399,11 +378,11 @@ export function runResponderEventsInBatch(
|
||||
handleTopLevelType(
|
||||
topLevelType,
|
||||
rootEventComponentFiber,
|
||||
context,
|
||||
responderEvent,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
processEventQueue(context._eventQueue);
|
||||
processEventQueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
|
||||
function createReactEventComponent(targetEventTypes, handleEvent) {
|
||||
function createReactEventComponent(targetEventTypes, onEvent) {
|
||||
const testEventResponder = {
|
||||
targetEventTypes,
|
||||
handleEvent,
|
||||
onEvent,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -53,19 +53,19 @@ describe('DOMEventResponderSystem', () => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
it('the event responder handleEvent() function should fire on click event', () => {
|
||||
it('the event responder onEvent() function should fire on click event', () => {
|
||||
let eventResponderFiredCount = 0;
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
(context, props) => {
|
||||
(event, context, props) => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
name: context.eventType,
|
||||
passive: context.isPassive(),
|
||||
passiveSupported: context.isPassiveSupported(),
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -79,7 +79,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
ReactDOM.render(<Test />, container);
|
||||
expect(container.innerHTML).toBe('<button>Click me!</button>');
|
||||
|
||||
// Clicking the button should trigger the event responder handleEvent()
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
expect(eventResponderFiredCount).toBe(1);
|
||||
@@ -103,7 +103,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
expect(eventResponderFiredCount).toBe(2);
|
||||
});
|
||||
|
||||
it('the event responder handleEvent() function should fire on click event (passive events forced)', () => {
|
||||
it('the event responder onEvent() function should fire on click event (passive events forced)', () => {
|
||||
// JSDOM does not support passive events, so this manually overrides the value to be true
|
||||
const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
|
||||
checkPassiveEvents.passiveBrowserEventsSupported = true;
|
||||
@@ -113,11 +113,11 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
(context, props) => {
|
||||
(event, context, props) => {
|
||||
eventLog.push({
|
||||
name: context.eventType,
|
||||
passive: context.isPassive(),
|
||||
passiveSupported: context.isPassiveSupported(),
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -130,7 +130,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Clicking the button should trigger the event responder handleEvent()
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
expect(eventLog.length).toBe(1);
|
||||
@@ -141,19 +141,19 @@ describe('DOMEventResponderSystem', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('nested event responders and their handleEvent() function should fire multiple times', () => {
|
||||
it('nested event responders and their onEvent() function should fire multiple times', () => {
|
||||
let eventResponderFiredCount = 0;
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
(context, props) => {
|
||||
(event, context, props) => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
name: context.eventType,
|
||||
passive: context.isPassive(),
|
||||
passiveSupported: context.isPassiveSupported(),
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -168,7 +168,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Clicking the button should trigger the event responder handleEvent()
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
expect(eventResponderFiredCount).toBe(2);
|
||||
@@ -186,7 +186,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('nested event responders and their handleEvent() should fire in the correct order', () => {
|
||||
it('nested event responders and their onEvent() should fire in the correct order', () => {
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
@@ -214,7 +214,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Clicking the button should trigger the event responder handleEvent()
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
|
||||
@@ -227,14 +227,14 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
(context, props) => {
|
||||
(event, context, props) => {
|
||||
if (props.onMagicClick) {
|
||||
const event = {
|
||||
const syntheticEvent = {
|
||||
listener: props.onMagicClick,
|
||||
target: context.eventTarget,
|
||||
target: event.target,
|
||||
type: 'magicclick',
|
||||
};
|
||||
context.dispatchEvent(event, {discrete: true});
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -251,7 +251,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Clicking the button should trigger the event responder handleEvent()
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
|
||||
@@ -264,37 +264,33 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
const LongPressEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
(context, props) => {
|
||||
(event, context, props) => {
|
||||
const pressEvent = {
|
||||
listener: props.onPress,
|
||||
target: context.eventTarget,
|
||||
target: event.target,
|
||||
type: 'press',
|
||||
};
|
||||
context.dispatchEvent(pressEvent, {discrete: true});
|
||||
|
||||
setTimeout(
|
||||
() =>
|
||||
context.withAsyncDispatching(() => {
|
||||
if (props.onLongPress) {
|
||||
const longPressEvent = {
|
||||
listener: props.onLongPress,
|
||||
target: context.eventTarget,
|
||||
type: 'longpress',
|
||||
};
|
||||
context.dispatchEvent(longPressEvent, {discrete: true});
|
||||
}
|
||||
context.setTimeout(() => {
|
||||
if (props.onLongPress) {
|
||||
const longPressEvent = {
|
||||
listener: props.onLongPress,
|
||||
target: event.target,
|
||||
type: 'longpress',
|
||||
};
|
||||
context.dispatchEvent(longPressEvent, {discrete: true});
|
||||
}
|
||||
|
||||
if (props.onLongPressChange) {
|
||||
const longPressChangeEvent = {
|
||||
listener: props.onLongPressChange,
|
||||
target: context.eventTarget,
|
||||
type: 'longpresschange',
|
||||
};
|
||||
context.dispatchEvent(longPressChangeEvent, {discrete: true});
|
||||
}
|
||||
}),
|
||||
500,
|
||||
);
|
||||
if (props.onLongPressChange) {
|
||||
const longPressChangeEvent = {
|
||||
listener: props.onLongPressChange,
|
||||
target: event.target,
|
||||
type: 'longpresschange',
|
||||
};
|
||||
context.dispatchEvent(longPressChangeEvent, {discrete: true});
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -313,7 +309,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Clicking the button should trigger the event responder handleEvent()
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
jest.runAllTimers();
|
||||
|
||||
Vendored
+20
-15
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
const targetEventTypes = ['pointerdown', 'pointercancel'];
|
||||
@@ -62,7 +62,7 @@ function createDragEvent(
|
||||
}
|
||||
|
||||
function dispatchDragEvent(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
name: DragEventType,
|
||||
listener: DragEvent => void,
|
||||
state: DragState,
|
||||
@@ -87,28 +87,31 @@ const DragResponder = {
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
handleEvent(
|
||||
context: EventResponderContext,
|
||||
onEvent(
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: Object,
|
||||
state: DragState,
|
||||
): void {
|
||||
const {eventTarget, eventType, event} = context;
|
||||
const {target, type, nativeEvent} = event;
|
||||
|
||||
switch (eventType) {
|
||||
switch (type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown':
|
||||
case 'pointerdown': {
|
||||
if (!state.isDragging) {
|
||||
if (props.onShouldClaimOwnership) {
|
||||
context.releaseOwnership(state.dragTarget);
|
||||
context.releaseOwnership();
|
||||
}
|
||||
const obj =
|
||||
eventType === 'touchstart' ? (event: any).changedTouches[0] : event;
|
||||
type === 'touchstart'
|
||||
? (nativeEvent: any).changedTouches[0]
|
||||
: nativeEvent;
|
||||
const x = (state.startX = (obj: any).screenX);
|
||||
const y = (state.startY = (obj: any).screenY);
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.dragTarget = eventTarget;
|
||||
state.dragTarget = target;
|
||||
state.isPointerDown = true;
|
||||
|
||||
if (props.onDragStart) {
|
||||
@@ -121,19 +124,21 @@ const DragResponder = {
|
||||
);
|
||||
}
|
||||
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'touchmove':
|
||||
case 'mousemove':
|
||||
case 'pointermove': {
|
||||
if (context.isPassive()) {
|
||||
if (event.passive) {
|
||||
return;
|
||||
}
|
||||
if (state.isPointerDown) {
|
||||
const obj =
|
||||
eventType === 'touchmove' ? (event: any).changedTouches[0] : event;
|
||||
type === 'touchmove'
|
||||
? (nativeEvent: any).changedTouches[0]
|
||||
: nativeEvent;
|
||||
const x = (obj: any).screenX;
|
||||
const y = (obj: any).screenY;
|
||||
state.x = x;
|
||||
@@ -145,7 +150,7 @@ const DragResponder = {
|
||||
props.onShouldClaimOwnership &&
|
||||
props.onShouldClaimOwnership()
|
||||
) {
|
||||
shouldEnableDragging = context.requestOwnership(state.dragTarget);
|
||||
shouldEnableDragging = context.requestOwnership();
|
||||
}
|
||||
if (shouldEnableDragging) {
|
||||
state.isDragging = true;
|
||||
@@ -181,7 +186,7 @@ const DragResponder = {
|
||||
eventData,
|
||||
);
|
||||
}
|
||||
(event: any).preventDefault();
|
||||
(nativeEvent: any).preventDefault();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -193,7 +198,7 @@ const DragResponder = {
|
||||
case 'pointerup': {
|
||||
if (state.isDragging) {
|
||||
if (props.onShouldClaimOwnership) {
|
||||
context.releaseOwnership(state.dragTarget);
|
||||
context.releaseOwnership();
|
||||
}
|
||||
if (props.onDragEnd) {
|
||||
dispatchDragEvent(context, 'dragend', props.onDragEnd, state, true);
|
||||
|
||||
Vendored
+21
-30
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
type FocusProps = {
|
||||
@@ -47,55 +47,45 @@ function createFocusEvent(
|
||||
}
|
||||
|
||||
function dispatchFocusInEvents(
|
||||
context: EventResponderContext,
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: FocusProps,
|
||||
) {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
const {nativeEvent, target} = event;
|
||||
if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
if (props.onFocus) {
|
||||
const syntheticEvent = createFocusEvent(
|
||||
'focus',
|
||||
eventTarget,
|
||||
props.onFocus,
|
||||
);
|
||||
const syntheticEvent = createFocusEvent('focus', target, props.onFocus);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
if (props.onFocusChange) {
|
||||
const listener = () => {
|
||||
props.onFocusChange(true);
|
||||
};
|
||||
const syntheticEvent = createFocusEvent(
|
||||
'focuschange',
|
||||
eventTarget,
|
||||
listener,
|
||||
);
|
||||
const syntheticEvent = createFocusEvent('focuschange', target, listener);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchFocusOutEvents(
|
||||
context: EventResponderContext,
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: FocusProps,
|
||||
) {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
const {nativeEvent, target} = event;
|
||||
if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
if (props.onBlur) {
|
||||
const syntheticEvent = createFocusEvent('blur', eventTarget, props.onBlur);
|
||||
const syntheticEvent = createFocusEvent('blur', target, props.onBlur);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
if (props.onFocusChange) {
|
||||
const listener = () => {
|
||||
props.onFocusChange(false);
|
||||
};
|
||||
const syntheticEvent = createFocusEvent(
|
||||
'focuschange',
|
||||
eventTarget,
|
||||
listener,
|
||||
);
|
||||
const syntheticEvent = createFocusEvent('focuschange', target, listener);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
}
|
||||
@@ -107,24 +97,25 @@ const FocusResponder = {
|
||||
isFocused: false,
|
||||
};
|
||||
},
|
||||
handleEvent(
|
||||
context: EventResponderContext,
|
||||
onEvent(
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: Object,
|
||||
state: FocusState,
|
||||
): void {
|
||||
const {eventTarget, eventType} = context;
|
||||
const {type} = event;
|
||||
|
||||
switch (eventType) {
|
||||
switch (type) {
|
||||
case 'focus': {
|
||||
if (!state.isFocused && !context.isTargetOwned(eventTarget)) {
|
||||
dispatchFocusInEvents(context, props);
|
||||
if (!state.isFocused && !context.hasOwnership()) {
|
||||
dispatchFocusInEvents(event, context, props);
|
||||
state.isFocused = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'blur': {
|
||||
if (state.isFocused) {
|
||||
dispatchFocusOutEvents(context, props);
|
||||
dispatchFocusOutEvents(event, context, props);
|
||||
state.isFocused = false;
|
||||
}
|
||||
break;
|
||||
|
||||
Vendored
+31
-40
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
type HoverProps = {
|
||||
@@ -61,17 +61,18 @@ function createHoverEvent(
|
||||
}
|
||||
|
||||
function dispatchHoverStartEvents(
|
||||
context: EventResponderContext,
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: HoverProps,
|
||||
): void {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
const {nativeEvent, target} = event;
|
||||
if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
if (props.onHoverStart) {
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hoverstart',
|
||||
eventTarget,
|
||||
target,
|
||||
props.onHoverStart,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
@@ -80,27 +81,24 @@ function dispatchHoverStartEvents(
|
||||
const listener = () => {
|
||||
props.onHoverChange(true);
|
||||
};
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hoverchange',
|
||||
eventTarget,
|
||||
listener,
|
||||
);
|
||||
const syntheticEvent = createHoverEvent('hoverchange', target, listener);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchHoverEndEvents(
|
||||
context: EventResponderContext,
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: HoverProps,
|
||||
) {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
const {nativeEvent, target} = event;
|
||||
if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
if (props.onHoverEnd) {
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hoverend',
|
||||
eventTarget,
|
||||
target,
|
||||
props.onHoverEnd,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
@@ -109,11 +107,7 @@ function dispatchHoverEndEvents(
|
||||
const listener = () => {
|
||||
props.onHoverChange(false);
|
||||
};
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hoverchange',
|
||||
eventTarget,
|
||||
listener,
|
||||
);
|
||||
const syntheticEvent = createHoverEvent('hoverchange', target, listener);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
}
|
||||
@@ -127,14 +121,15 @@ const HoverResponder = {
|
||||
isTouched: false,
|
||||
};
|
||||
},
|
||||
handleEvent(
|
||||
context: EventResponderContext,
|
||||
onEvent(
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: HoverProps,
|
||||
state: HoverState,
|
||||
): void {
|
||||
const {eventType, eventTarget, event} = context;
|
||||
const {type, nativeEvent} = event;
|
||||
|
||||
switch (eventType) {
|
||||
switch (type) {
|
||||
/**
|
||||
* Prevent hover events when touch is being used.
|
||||
*/
|
||||
@@ -147,25 +142,21 @@ const HoverResponder = {
|
||||
|
||||
case 'pointerover':
|
||||
case 'mouseover': {
|
||||
if (
|
||||
!state.isHovered &&
|
||||
!state.isTouched &&
|
||||
!context.isTargetOwned(eventTarget)
|
||||
) {
|
||||
if ((event: any).pointerType === 'touch') {
|
||||
if (!state.isHovered && !state.isTouched && !context.hasOwnership()) {
|
||||
if ((nativeEvent: any).pointerType === 'touch') {
|
||||
state.isTouched = true;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
(event: any).x,
|
||||
(event: any).y,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
)
|
||||
) {
|
||||
state.isInHitSlop = true;
|
||||
return;
|
||||
}
|
||||
dispatchHoverStartEvents(context, props);
|
||||
dispatchHoverStartEvents(event, context, props);
|
||||
state.isHovered = true;
|
||||
}
|
||||
break;
|
||||
@@ -173,7 +164,7 @@ const HoverResponder = {
|
||||
case 'pointerout':
|
||||
case 'mouseout': {
|
||||
if (state.isHovered && !state.isTouched) {
|
||||
dispatchHoverEndEvents(context, props);
|
||||
dispatchHoverEndEvents(event, context, props);
|
||||
state.isHovered = false;
|
||||
}
|
||||
state.isInHitSlop = false;
|
||||
@@ -185,22 +176,22 @@ const HoverResponder = {
|
||||
if (state.isInHitSlop) {
|
||||
if (
|
||||
!context.isPositionWithinTouchHitTarget(
|
||||
(event: any).x,
|
||||
(event: any).y,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
)
|
||||
) {
|
||||
dispatchHoverStartEvents(context, props);
|
||||
dispatchHoverStartEvents(event, context, props);
|
||||
state.isHovered = true;
|
||||
state.isInHitSlop = false;
|
||||
}
|
||||
} else if (
|
||||
state.isHovered &&
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
(event: any).x,
|
||||
(event: any).y,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
)
|
||||
) {
|
||||
dispatchHoverEndEvents(context, props);
|
||||
dispatchHoverEndEvents(event, context, props);
|
||||
state.isHovered = false;
|
||||
state.isInHitSlop = true;
|
||||
}
|
||||
@@ -209,7 +200,7 @@ const HoverResponder = {
|
||||
}
|
||||
case 'pointercancel': {
|
||||
if (state.isHovered && !state.isTouched) {
|
||||
dispatchHoverEndEvents(context, props);
|
||||
dispatchHoverEndEvents(event, context, props);
|
||||
state.isHovered = false;
|
||||
state.isTouched = false;
|
||||
}
|
||||
|
||||
Vendored
+83
-59
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
type PressProps = {
|
||||
@@ -86,7 +86,7 @@ function createPressEvent(
|
||||
}
|
||||
|
||||
function dispatchEvent(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
state: PressState,
|
||||
name: PressEventType,
|
||||
listener: (e: Object) => void,
|
||||
@@ -97,7 +97,7 @@ function dispatchEvent(
|
||||
}
|
||||
|
||||
function dispatchPressChangeEvent(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
@@ -108,7 +108,7 @@ function dispatchPressChangeEvent(
|
||||
}
|
||||
|
||||
function dispatchLongPressChangeEvent(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
@@ -119,7 +119,7 @@ function dispatchLongPressChangeEvent(
|
||||
}
|
||||
|
||||
function dispatchPressStartEvents(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
@@ -138,34 +138,30 @@ function dispatchPressStartEvents(
|
||||
DEFAULT_LONG_PRESS_DELAY_MS,
|
||||
);
|
||||
|
||||
state.longPressTimeout = setTimeout(
|
||||
() =>
|
||||
context.withAsyncDispatching(() => {
|
||||
state.isLongPressed = true;
|
||||
state.longPressTimeout = null;
|
||||
state.longPressTimeout = context.setTimeout(() => {
|
||||
state.isLongPressed = true;
|
||||
state.longPressTimeout = null;
|
||||
|
||||
if (props.onLongPress) {
|
||||
const listener = e => {
|
||||
props.onLongPress(e);
|
||||
// TODO address this again at some point
|
||||
// if (e.nativeEvent.defaultPrevented) {
|
||||
// state.defaultPrevented = true;
|
||||
// }
|
||||
};
|
||||
dispatchEvent(context, state, 'longpress', listener);
|
||||
}
|
||||
if (props.onLongPress) {
|
||||
const listener = e => {
|
||||
props.onLongPress(e);
|
||||
// TODO address this again at some point
|
||||
// if (e.nativeEvent.defaultPrevented) {
|
||||
// state.defaultPrevented = true;
|
||||
// }
|
||||
};
|
||||
dispatchEvent(context, state, 'longpress', listener);
|
||||
}
|
||||
|
||||
if (props.onLongPressChange) {
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
}
|
||||
}),
|
||||
delayLongPress,
|
||||
);
|
||||
if (props.onLongPressChange) {
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
}
|
||||
}, delayLongPress);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPressEndEvents(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
@@ -206,6 +202,21 @@ function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
|
||||
return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
|
||||
}
|
||||
|
||||
function unmountResponder(
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (state.isPressed) {
|
||||
state.isPressed = false;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
if (state.longPressTimeout !== null) {
|
||||
clearTimeout(state.longPressTimeout);
|
||||
state.longPressTimeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PressResponder = {
|
||||
targetEventTypes,
|
||||
createInitialState(): PressState {
|
||||
@@ -219,14 +230,15 @@ const PressResponder = {
|
||||
shouldSkipMouseAfterTouch: false,
|
||||
};
|
||||
},
|
||||
handleEvent(
|
||||
context: EventResponderContext,
|
||||
onEvent(
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const {eventTarget, eventType, event} = context;
|
||||
const {target, type, nativeEvent} = event;
|
||||
|
||||
switch (eventType) {
|
||||
switch (type) {
|
||||
/**
|
||||
* Respond to pointer events and fall back to mouse.
|
||||
*/
|
||||
@@ -234,29 +246,29 @@ const PressResponder = {
|
||||
case 'mousedown': {
|
||||
if (
|
||||
!state.isPressed &&
|
||||
!context.isTargetOwned(eventTarget) &&
|
||||
!context.hasOwnership() &&
|
||||
!state.shouldSkipMouseAfterTouch
|
||||
) {
|
||||
if (
|
||||
(event: any).pointerType === 'mouse' ||
|
||||
eventType === 'mousedown'
|
||||
(nativeEvent: any).pointerType === 'mouse' ||
|
||||
type === 'mousedown'
|
||||
) {
|
||||
if (
|
||||
// Ignore right- and middle-clicks
|
||||
event.button === 1 ||
|
||||
event.button === 2 ||
|
||||
nativeEvent.button === 1 ||
|
||||
nativeEvent.button === 2 ||
|
||||
// Ignore pressing on hit slop area with mouse
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
(event: any).x,
|
||||
(event: any).y,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
state.pressTarget = target;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -273,7 +285,7 @@ const PressResponder = {
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
|
||||
if (state.pressTarget !== null && props.onPress) {
|
||||
if (context.isTargetWithinElement(eventTarget, state.pressTarget)) {
|
||||
if (context.isTargetWithinElement(target, state.pressTarget)) {
|
||||
if (
|
||||
!(
|
||||
wasLongPressed &&
|
||||
@@ -303,16 +315,16 @@ const PressResponder = {
|
||||
* support for pointer events.
|
||||
*/
|
||||
case 'touchstart': {
|
||||
if (!state.isPressed && !context.isTargetOwned(eventTarget)) {
|
||||
if (!state.isPressed && !context.hasOwnership()) {
|
||||
// We bail out of polyfilling anchor tags, given the same heuristics
|
||||
// explained above in regards to needing to use click events.
|
||||
if (isAnchorTagElement(eventTarget)) {
|
||||
if (isAnchorTagElement(target)) {
|
||||
state.isAnchorTouched = true;
|
||||
return;
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
state.pressTarget = target;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -326,17 +338,17 @@ const PressResponder = {
|
||||
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
|
||||
if (eventType !== 'touchcancel' && props.onPress) {
|
||||
if (type !== 'touchcancel' && props.onPress) {
|
||||
// Find if the X/Y of the end touch is still that of the original target
|
||||
const changedTouch = (event: any).changedTouches[0];
|
||||
const doc = (eventTarget: any).ownerDocument;
|
||||
const target = doc.elementFromPoint(
|
||||
const changedTouch = (nativeEvent: any).changedTouches[0];
|
||||
const doc = (target: any).ownerDocument;
|
||||
const fromTarget = doc.elementFromPoint(
|
||||
changedTouch.screenX,
|
||||
changedTouch.screenY,
|
||||
);
|
||||
if (
|
||||
target !== null &&
|
||||
context.isTargetWithinEventComponent(target)
|
||||
fromTarget !== null &&
|
||||
context.isTargetWithinEventComponent(fromTarget)
|
||||
) {
|
||||
if (
|
||||
!(
|
||||
@@ -363,21 +375,21 @@ const PressResponder = {
|
||||
if (
|
||||
!state.isPressed &&
|
||||
!state.isLongPressed &&
|
||||
!context.isTargetOwned(eventTarget) &&
|
||||
isValidKeyPress((event: any).key)
|
||||
!context.hasOwnership() &&
|
||||
isValidKeyPress((nativeEvent: any).key)
|
||||
) {
|
||||
// Prevent spacebar press from scrolling the window
|
||||
if ((event: any).key === ' ') {
|
||||
(event: any).preventDefault();
|
||||
if ((nativeEvent: any).key === ' ') {
|
||||
(nativeEvent: any).preventDefault();
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
state.pressTarget = target;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'keyup': {
|
||||
if (state.isPressed && isValidKeyPress((event: any).key)) {
|
||||
if (state.isPressed && isValidKeyPress((nativeEvent: any).key)) {
|
||||
const wasLongPressed = state.isLongPressed;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
if (state.pressTarget !== null && props.onPress) {
|
||||
@@ -410,12 +422,24 @@ const PressResponder = {
|
||||
|
||||
case 'click': {
|
||||
if (state.defaultPrevented) {
|
||||
(event: any).preventDefault();
|
||||
(nativeEvent: any).preventDefault();
|
||||
state.defaultPrevented = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO This method doesn't work as of yet
|
||||
onUnmount(context: ResponderContext, props: PressProps, state: PressState) {
|
||||
unmountResponder(context, props, state);
|
||||
},
|
||||
// TODO This method doesn't work as of yet
|
||||
onOwnershipChange(
|
||||
context: ResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
) {
|
||||
unmountResponder(context, props, state);
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
Vendored
+19
-18
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
const targetEventTypes = ['pointerdown', 'pointercancel'];
|
||||
@@ -52,7 +52,7 @@ function createSwipeEvent(
|
||||
}
|
||||
|
||||
function dispatchSwipeEvent(
|
||||
context: EventResponderContext,
|
||||
context: ResponderContext,
|
||||
name: SwipeEventType,
|
||||
listener: SwipeEvent => void,
|
||||
state: SwipeState,
|
||||
@@ -91,21 +91,22 @@ const SwipeResponder = {
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
handleEvent(
|
||||
context: EventResponderContext,
|
||||
onEvent(
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: Object,
|
||||
state: SwipeState,
|
||||
): void {
|
||||
const {eventTarget, eventType, event} = context;
|
||||
const {target, type, nativeEvent} = event;
|
||||
|
||||
switch (eventType) {
|
||||
switch (type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown':
|
||||
case 'pointerdown': {
|
||||
if (!state.isSwiping && !context.isTargetOwned(eventTarget)) {
|
||||
if (!state.isSwiping && !context.hasOwnership()) {
|
||||
let obj = event;
|
||||
if (eventType === 'touchstart') {
|
||||
obj = (event: any).targetTouches[0];
|
||||
if (type === 'touchstart') {
|
||||
obj = (nativeEvent: any).targetTouches[0];
|
||||
state.touchId = obj.identifier;
|
||||
}
|
||||
const x = (obj: any).screenX;
|
||||
@@ -114,7 +115,7 @@ const SwipeResponder = {
|
||||
let shouldEnableSwiping = true;
|
||||
|
||||
if (props.onShouldClaimOwnership && props.onShouldClaimOwnership()) {
|
||||
shouldEnableSwiping = context.requestOwnership(eventTarget);
|
||||
shouldEnableSwiping = context.requestOwnership();
|
||||
}
|
||||
if (shouldEnableSwiping) {
|
||||
state.isSwiping = true;
|
||||
@@ -122,8 +123,8 @@ const SwipeResponder = {
|
||||
state.startY = y;
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.swipeTarget = eventTarget;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
state.swipeTarget = target;
|
||||
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
|
||||
} else {
|
||||
state.touchId = null;
|
||||
}
|
||||
@@ -133,13 +134,13 @@ const SwipeResponder = {
|
||||
case 'touchmove':
|
||||
case 'mousemove':
|
||||
case 'pointermove': {
|
||||
if (context.isPassive()) {
|
||||
if (event.passive) {
|
||||
return;
|
||||
}
|
||||
if (state.isSwiping) {
|
||||
let obj = null;
|
||||
if (eventType === 'touchmove') {
|
||||
const targetTouches = (event: any).targetTouches;
|
||||
if (type === 'touchmove') {
|
||||
const targetTouches = (nativeEvent: any).targetTouches;
|
||||
for (let i = 0; i < targetTouches.length; i++) {
|
||||
if (state.touchId === targetTouches[i].identifier) {
|
||||
obj = targetTouches[i];
|
||||
@@ -147,7 +148,7 @@ const SwipeResponder = {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj = event;
|
||||
obj = nativeEvent;
|
||||
}
|
||||
if (obj === null) {
|
||||
state.isSwiping = false;
|
||||
@@ -178,7 +179,7 @@ const SwipeResponder = {
|
||||
false,
|
||||
eventData,
|
||||
);
|
||||
(event: any).preventDefault();
|
||||
(nativeEvent: any).preventDefault();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -193,7 +194,7 @@ const SwipeResponder = {
|
||||
return;
|
||||
}
|
||||
if (props.onShouldClaimOwnership) {
|
||||
context.releaseOwnership(state.swipeTarget);
|
||||
context.releaseOwnership();
|
||||
}
|
||||
const direction = state.direction;
|
||||
const lastDirection = state.lastDirection;
|
||||
|
||||
@@ -22,7 +22,7 @@ let TouchHitTarget;
|
||||
|
||||
const noOpResponder = {
|
||||
targetEventTypes: [],
|
||||
handleEvent() {},
|
||||
onEvent() {},
|
||||
};
|
||||
|
||||
function createReactEventComponent() {
|
||||
|
||||
+1
@@ -624,6 +624,7 @@ export function createFiberFromEventComponent(
|
||||
fiber.elementType = eventComponent;
|
||||
fiber.type = eventComponent;
|
||||
fiber.stateNode = {
|
||||
context: null,
|
||||
props: pendingProps,
|
||||
state: null,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
|
||||
|
||||
export type ReactNode =
|
||||
| React$Element<any>
|
||||
| ReactPortal
|
||||
@@ -88,7 +90,18 @@ export type ReactEventResponderEventType =
|
||||
export type ReactEventResponder = {
|
||||
targetEventTypes: Array<ReactEventResponderEventType>,
|
||||
createInitialState?: (props: Object) => Object,
|
||||
handleEvent: (context: Object, props: Object, state: Object) => void,
|
||||
onEvent: (
|
||||
event: ResponderEvent,
|
||||
context: ResponderContext,
|
||||
props: Object,
|
||||
state: Object,
|
||||
) => void,
|
||||
onUnmount: (context: ResponderContext, props: Object, state: Object) => void,
|
||||
onOwnershipChange: (
|
||||
context: ResponderContext,
|
||||
props: Object,
|
||||
state: Object,
|
||||
) => void,
|
||||
};
|
||||
|
||||
export type ReactEventComponent = {|
|
||||
|
||||
Reference in New Issue
Block a user