@samitouri / QOS-React / commits / b93a8a9bb8

Experimental event API: refactor responder modules for lifecycle inclusion (#15322)

Dominic Gannaway committed Apr 4, 2019 at 23:28 UTC b93a8a9bb8460a3d582072d3b252ecc15c6ea0f5
11 files changed +485 -478
packages/events/EventTypes.js
+23 -17
@@ -10,34 +10,40 @@
10 import type {AnyNativeEvent} from 'events/PluginModuleType';
11 import type {ReactEventResponderEventType} from 'shared/ReactTypes';
12
13 -export type EventResponderContext = {
14 - event: AnyNativeEvent,
15 - eventTarget: Element | Document,
16 - eventType: string,
17 - isPassive: () => boolean,
18 - isPassiveSupported: () => boolean,
19 - dispatchEvent: <E>(
20 - eventObject: E,
21 - {
22 - capture?: boolean,
23 - discrete?: boolean,
24 - stopPropagation?: boolean,
25 - },
13 +export type ResponderEvent = {
14 + nativeEvent: AnyNativeEvent,
15 + target: Element | Document,
16 + type: string,
17 + passive: boolean,
18 + passiveSupported: boolean,
19 +};
20 +
21 +export type ResponderDispatchEventOptions = {
22 + capture?: boolean,
23 + discrete?: boolean,
24 + stopPropagation?: boolean,
25 +};
26 +
27 +export type ResponderContext = {
28 + dispatchEvent: (
29 + eventObject: Object,
30 + otpions: ResponderDispatchEventOptions,
31 ) => void,
32 isTargetWithinElement: (
33 childTarget: Element | Document,
34 parentTarget: Element | Document,
35 ) => boolean,
31 - isTargetOwned: (Element | Document) => boolean,
36 isTargetWithinEventComponent: (Element | Document) => boolean,
37 isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
38 addRootEventTypes: (
39 + document: Document,
40 rootEventTypes: Array<ReactEventResponderEventType>,
41 ) => void,
42 removeRootEventTypes: (
43 rootEventTypes: Array<ReactEventResponderEventType>,
44 ) => void,
40 - requestOwnership: (target: Element | Document | null) => boolean,
41 - releaseOwnership: (target: Element | Document | null) => boolean,
42 - withAsyncDispatching: (func: () => void) => void,
45 + hasOwnership: () => boolean,
46 + requestOwnership: () => boolean,
47 + releaseOwnership: () => boolean,
48 + setTimeout: (func: () => void, timeout: number) => TimeoutID,
49 };
packages/react-dom/src/events/DOMEventResponderSystem.js
+222 -243
@@ -6,6 +6,11 @@
6 * @flow
7 */
8
9 +import type {
10 + ResponderContext,
11 + ResponderEvent,
12 + ResponderDispatchEventOptions,
13 +} from 'events/EventTypes';
14 import {
15 type EventSystemFlags,
16 IS_PASSIVE,
@@ -20,13 +25,13 @@ import type {
25 import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
26 import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
27 import type {Fiber} from 'react-reconciler/src/ReactFiber';
23 -import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
24 -
28 +import warning from 'shared/warning';
29 import {enableEventAPI} from 'shared/ReactFeatureFlags';
30 import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
27 -import warning from 'shared/warning';
31
29 -let listenToResponderEventTypesImpl;
32 +import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
33 +
34 +export let listenToResponderEventTypesImpl;
35
36 export function setListenToResponderEventTypes(
37 _listenToResponderEventTypesImpl: Function,
@@ -34,31 +39,215 @@ export function setListenToResponderEventTypes(
39 listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl;
40 }
41
37 -const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
42 +type EventQueue = {
43 + bubble: null | Array<$Shape<PartialEventObject>>,
44 + capture: null | Array<$Shape<PartialEventObject>>,
45 + discrete: boolean,
46 +};
47 +
48 +type PartialEventObject = {
49 + listener: ($Shape<PartialEventObject>) => void,
50 + target: Element | Document,
51 + type: string,
52 +};
53 +
54 +let currentOwner = null;
55 +let currentFiber: Fiber;
56 +let currentResponder: ReactEventResponder;
57 +let currentEventQueue: EventQueue;
58 +
59 +const eventResponderContext: ResponderContext = {
60 + dispatchEvent(
61 + possibleEventObject: Object,
62 + {capture, discrete, stopPropagation}: ResponderDispatchEventOptions,
63 + ): void {
64 + const eventQueue = currentEventQueue;
65 + const {listener, target, type} = possibleEventObject;
66 +
67 + if (listener == null || target == null || type == null) {
68 + throw new Error(
69 + 'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
70 + );
71 + }
72 + if (__DEV__) {
73 + possibleEventObject.preventDefault = () => {
74 + // Update this warning when we have a story around dealing with preventDefault
75 + warning(
76 + false,
77 + 'preventDefault() is no longer available on event objects created from event responder modules.',
78 + );
79 + };
80 + possibleEventObject.stopPropagation = () => {
81 + // Update this warning when we have a story around dealing with stopPropgation
82 + warning(
83 + false,
84 + 'stopPropagation() is no longer available on event objects created from event responder modules.',
85 + );
86 + };
87 + }
88 + const eventObject = ((possibleEventObject: any): $Shape<
89 + PartialEventObject,
90 + >);
91 + let events;
92 +
93 + if (capture) {
94 + events = eventQueue.capture;
95 + if (events === null) {
96 + events = eventQueue.capture = [];
97 + }
98 + } else {
99 + events = eventQueue.bubble;
100 + if (events === null) {
101 + events = eventQueue.bubble = [];
102 + }
103 + }
104 + if (discrete) {
105 + eventQueue.discrete = true;
106 + }
107 + events.push(eventObject);
108 +
109 + if (stopPropagation) {
110 + eventsWithStopPropagation.add(eventObject);
111 + }
112 + },
113 + isPositionWithinTouchHitTarget(x: number, y: number): boolean {
114 + return false;
115 + },
116 + isTargetWithinEventComponent(target: Element | Document): boolean {
117 + const eventFiber = currentFiber;
118 +
119 + if (target != null) {
120 + let fiber = getClosestInstanceFromNode(target);
121 + while (fiber !== null) {
122 + if (fiber === eventFiber || fiber === eventFiber.alternate) {
123 + return true;
124 + }
125 + fiber = fiber.return;
126 + }
127 + }
128 + return false;
129 + },
130 + isTargetWithinElement(
131 + childTarget: Element | Document,
132 + parentTarget: Element | Document,
133 + ): boolean {
134 + const childFiber = getClosestInstanceFromNode(childTarget);
135 + const parentFiber = getClosestInstanceFromNode(parentTarget);
136 +
137 + let node = childFiber;
138 + while (node !== null) {
139 + if (node === parentFiber) {
140 + return true;
141 + }
142 + node = node.return;
143 + }
144 + return false;
145 + },
146 + addRootEventTypes(
147 + doc: Document,
148 + rootEventTypes: Array<ReactEventResponderEventType>,
149 + ): void {
150 + listenToResponderEventTypesImpl(rootEventTypes, doc);
151 + const eventComponent = currentFiber;
152 + for (let i = 0; i < rootEventTypes.length; i++) {
153 + const rootEventType = rootEventTypes[i];
154 + const topLevelEventType =
155 + typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
156 + let rootEventComponents = rootEventTypesToEventComponents.get(
157 + topLevelEventType,
158 + );
159 + if (rootEventComponents === undefined) {
160 + rootEventComponents = new Set();
161 + rootEventTypesToEventComponents.set(
162 + topLevelEventType,
163 + rootEventComponents,
164 + );
165 + }
166 + rootEventComponents.add(eventComponent);
167 + }
168 + },
169 + removeRootEventTypes(
170 + rootEventTypes: Array<ReactEventResponderEventType>,
171 + ): void {
172 + const eventComponent = currentFiber;
173 + for (let i = 0; i < rootEventTypes.length; i++) {
174 + const rootEventType = rootEventTypes[i];
175 + const topLevelEventType =
176 + typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
177 + let rootEventComponents = rootEventTypesToEventComponents.get(
178 + topLevelEventType,
179 + );
180 + if (rootEventComponents !== undefined) {
181 + rootEventComponents.delete(eventComponent);
182 + }
183 + }
184 + },
185 + hasOwnership(): boolean {
186 + return currentOwner === currentFiber;
187 + },
188 + requestOwnership(): boolean {
189 + if (currentOwner !== null) {
190 + return false;
191 + }
192 + currentOwner = currentFiber;
193 + return true;
194 + },
195 + releaseOwnership(): boolean {
196 + if (currentOwner !== currentFiber) {
197 + return false;
198 + }
199 + currentOwner = null;
200 + return false;
201 + },
202 + setTimeout(func: () => void, delay): TimeoutID {
203 + const contextResponder = currentResponder;
204 + const contextFiber = currentFiber;
205 + return setTimeout(() => {
206 + const previousEventQueue = currentEventQueue;
207 + const previousFiber = currentFiber;
208 + const previousResponder = currentResponder;
209 + currentEventQueue = createEventQueue();
210 + currentResponder = contextResponder;
211 + currentFiber = contextFiber;
212 + try {
213 + func();
214 + batchedUpdates(processEventQueue, currentEventQueue);
215 + } finally {
216 + currentFiber = previousFiber;
217 + currentEventQueue = previousEventQueue;
218 + currentResponder = previousResponder;
219 + }
220 + }, delay);
221 + },
222 +};
223
224 const rootEventTypesToEventComponents: Map<
225 DOMTopLevelEventType | string,
226 Set<Fiber>,
227 > = new Map();
228 +const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
229 +const eventsWithStopPropagation:
230 + | WeakSet
231 + | Set<$Shape<PartialEventObject>> = new PossiblyWeakSet();
232 const targetEventTypeCached: Map<
233 Array<ReactEventResponderEventType>,
234 Set<DOMTopLevelEventType>,
235 > = new Map();
47 -const targetOwnership: Map<EventTarget, Fiber> = new Map();
48 -const eventsWithStopPropagation:
49 - | WeakSet
50 - | Set<$Shape<PartialEventObject>> = new PossiblyWeakSet();
236
52 -type PartialEventObject = {
53 - listener: ($Shape<PartialEventObject>) => void,
54 - target: Element | Document,
55 - type: string,
56 -};
57 -type EventQueue = {
58 - bubble: null | Array<$Shape<PartialEventObject>>,
59 - capture: null | Array<$Shape<PartialEventObject>>,
60 - discrete: boolean,
61 -};
237 +function createResponderEvent(
238 + topLevelType: string,
239 + nativeEvent: AnyNativeEvent,
240 + nativeEventTarget: Element | Document,
241 + eventSystemFlags: EventSystemFlags,
242 +): ResponderEvent {
243 + return {
244 + nativeEvent: nativeEvent,
245 + target: nativeEventTarget,
246 + type: topLevelType,
247 + passive: (eventSystemFlags & IS_PASSIVE) !== 0,
248 + passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0,
249 + };
250 +}
251
252 function createEventQueue(): EventQueue {
253 return {
@@ -100,8 +289,8 @@ function processEvents(
289 }
290 }
291
103 -function processEventQueue(eventQueue: EventQueue): void {
104 - const {bubble, capture, discrete} = eventQueue;
292 +export function processEventQueue(): void {
293 + const {bubble, capture, discrete} = currentEventQueue;
294
295 if (discrete) {
296 interactiveUpdates(() => {
@@ -112,218 +301,6 @@ function processEventQueue(eventQueue: EventQueue): void {
301 }
302 }
303
115 -// TODO add context methods for dispatching events
116 -function DOMEventResponderContext(
117 - topLevelType: DOMTopLevelEventType,
118 - nativeEvent: AnyNativeEvent,
119 - nativeEventTarget: EventTarget,
120 - eventSystemFlags: EventSystemFlags,
121 -) {
122 - this.event = nativeEvent;
123 - this.eventTarget = nativeEventTarget;
124 - this.eventType = topLevelType;
125 - this._flags = eventSystemFlags;
126 - this._fiber = null;
127 - this._responder = null;
128 - this._discreteEvents = null;
129 - this._nonDiscreteEvents = null;
130 - this._isBatching = true;
131 - this._eventQueue = createEventQueue();
132 -}
133 -
134 -DOMEventResponderContext.prototype.isPassive = function(): boolean {
135 - return (this._flags & IS_PASSIVE) !== 0;
136 -};
137 -
138 -DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean {
139 - return (this._flags & PASSIVE_NOT_SUPPORTED) === 0;
140 -};
141 -
142 -DOMEventResponderContext.prototype.dispatchEvent = function(
143 - possibleEventObject: Object,
144 - {
145 - capture,
146 - discrete,
147 - stopPropagation,
148 - }: {
149 - capture?: boolean,
150 - discrete?: boolean,
151 - stopPropagation?: boolean,
152 - },
153 -): void {
154 - const eventQueue = this._eventQueue;
155 - const {listener, target, type} = possibleEventObject;
156 -
157 - if (listener == null || target == null || type == null) {
158 - throw new Error(
159 - 'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
160 - );
161 - }
162 - if (__DEV__) {
163 - possibleEventObject.preventDefault = () => {
164 - // Update this warning when we have a story around dealing with preventDefault
165 - warning(
166 - false,
167 - 'preventDefault() is no longer available on event objects created from event responder modules.',
168 - );
169 - };
170 - possibleEventObject.stopPropagation = () => {
171 - // Update this warning when we have a story around dealing with stopPropgation
172 - warning(
173 - false,
174 - 'stopPropagation() is no longer available on event objects created from event responder modules.',
175 - );
176 - };
177 - }
178 - const eventObject = ((possibleEventObject: any): $Shape<PartialEventObject>);
179 - let events;
180 -
181 - if (capture) {
182 - events = eventQueue.capture;
183 - if (events === null) {
184 - events = eventQueue.capture = [];
185 - }
186 - } else {
187 - events = eventQueue.bubble;
188 - if (events === null) {
189 - events = eventQueue.bubble = [];
190 - }
191 - }
192 - if (discrete) {
193 - eventQueue.discrete = true;
194 - }
195 - events.push(eventObject);
196 -
197 - if (stopPropagation) {
198 - eventsWithStopPropagation.add(eventObject);
199 - }
200 -};
201 -
202 -DOMEventResponderContext.prototype.isTargetWithinEventComponent = function(
203 - target: AnyNativeEvent,
204 -): boolean {
205 - const eventFiber = this._fiber;
206 -
207 - if (target != null) {
208 - let fiber = getClosestInstanceFromNode(target);
209 - while (fiber !== null) {
210 - if (fiber === eventFiber || fiber === eventFiber.alternate) {
211 - return true;
212 - }
213 - fiber = fiber.return;
214 - }
215 - }
216 - return false;
217 -};
218 -
219 -DOMEventResponderContext.prototype.isTargetWithinElement = function(
220 - childTarget: EventTarget,
221 - parentTarget: EventTarget,
222 -): boolean {
223 - const childFiber = getClosestInstanceFromNode(childTarget);
224 - const parentFiber = getClosestInstanceFromNode(parentTarget);
225 -
226 - let currentFiber = childFiber;
227 - while (currentFiber !== null) {
228 - if (currentFiber === parentFiber) {
229 - return true;
230 - }
231 - currentFiber = currentFiber.return;
232 - }
233 - return false;
234 -};
235 -
236 -DOMEventResponderContext.prototype.addRootEventTypes = function(
237 - rootEventTypes: Array<ReactEventResponderEventType>,
238 -) {
239 - const element = this.eventTarget.ownerDocument;
240 - listenToResponderEventTypesImpl(rootEventTypes, element);
241 - const eventComponent = this._fiber;
242 - for (let i = 0; i < rootEventTypes.length; i++) {
243 - const rootEventType = rootEventTypes[i];
244 - const topLevelEventType =
245 - typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
246 - let rootEventComponents = rootEventTypesToEventComponents.get(
247 - topLevelEventType,
248 - );
249 - if (rootEventComponents === undefined) {
250 - rootEventComponents = new Set();
251 - rootEventTypesToEventComponents.set(
252 - topLevelEventType,
253 - rootEventComponents,
254 - );
255 - }
256 - rootEventComponents.add(eventComponent);
257 - }
258 -};
259 -
260 -DOMEventResponderContext.prototype.removeRootEventTypes = function(
261 - rootEventTypes: Array<ReactEventResponderEventType>,
262 -): void {
263 - const eventComponent = this._fiber;
264 - for (let i = 0; i < rootEventTypes.length; i++) {
265 - const rootEventType = rootEventTypes[i];
266 - const topLevelEventType =
267 - typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
268 - let rootEventComponents = rootEventTypesToEventComponents.get(
269 - topLevelEventType,
270 - );
271 - if (rootEventComponents !== undefined) {
272 - rootEventComponents.delete(eventComponent);
273 - }
274 - }
275 -};
276 -
277 -DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() {
278 - // TODO
279 -};
280 -
281 -DOMEventResponderContext.prototype.isTargetOwned = function(
282 - targetElement: Element | Node,
283 -): boolean {
284 - const targetDoc = targetElement.ownerDocument;
285 - return targetOwnership.has(targetDoc);
286 -};
287 -
288 -DOMEventResponderContext.prototype.requestOwnership = function(
289 - targetElement: Element | Node,
290 -): boolean {
291 - const targetDoc = targetElement.ownerDocument;
292 - if (targetOwnership.has(targetDoc)) {
293 - return false;
294 - }
295 - targetOwnership.set(targetDoc, this._fiber);
296 - return true;
297 -};
298 -
299 -DOMEventResponderContext.prototype.releaseOwnership = function(
300 - targetElement: Element | Node,
301 -): boolean {
302 - const targetDoc = targetElement.ownerDocument;
303 - if (!targetOwnership.has(targetDoc)) {
304 - return false;
305 - }
306 - const owner = targetOwnership.get(targetDoc);
307 - if (owner === this._fiber || owner === this._fiber.alternate) {
308 - targetOwnership.delete(targetDoc);
309 - return true;
310 - }
311 - return false;
312 -};
313 -
314 -DOMEventResponderContext.prototype.withAsyncDispatching = function(
315 - func: () => void,
316 -) {
317 - const previousEventQueue = this._eventQueue;
318 - this._eventQueue = createEventQueue();
319 - try {
320 - func();
321 - batchedUpdates(processEventQueue, this._eventQueue);
322 - } finally {
323 - this._eventQueue = previousEventQueue;
324 - }
325 -};
326 -
304 function getTargetEventTypes(
305 eventTypes: Array<ReactEventResponderEventType>,
306 ): Set<DOMTopLevelEventType> {
@@ -345,7 +322,7 @@ function getTargetEventTypes(
322 function handleTopLevelType(
323 topLevelType: DOMTopLevelEventType,
324 fiber: Fiber,
348 - context: Object,
325 + responderEvent: ResponderEvent,
326 isRootLevelEvent: boolean,
327 ): void {
328 const responder: ReactEventResponder = fiber.type.responder;
@@ -360,9 +337,10 @@ function handleTopLevelType(
337 if (state === null && responder.createInitialState !== undefined) {
338 state = fiber.stateNode.state = responder.createInitialState(props);
339 }
363 - context._fiber = fiber;
364 - context._responder = responder;
365 - responder.handleEvent(context, props, state);
340 + currentFiber = fiber;
341 + currentResponder = responder;
342 +
343 + responder.onEvent(responderEvent, eventResponderContext, props, state);
344 }
345
346 export function runResponderEventsInBatch(
@@ -373,17 +351,18 @@ export function runResponderEventsInBatch(
351 eventSystemFlags: EventSystemFlags,
352 ): void {
353 if (enableEventAPI) {
376 - const context = new DOMEventResponderContext(
377 - topLevelType,
354 + currentEventQueue = createEventQueue();
355 + const responderEvent = createResponderEvent(
356 + ((topLevelType: any): string),
357 nativeEvent,
379 - nativeEventTarget,
358 + ((nativeEventTarget: any): Element | Document),
359 eventSystemFlags,
360 );
361 let node = targetFiber;
362 // Traverse up the fiber tree till we find event component fibers.
363 while (node !== null) {
364 if (node.tag === EventComponent) {
386 - handleTopLevelType(topLevelType, node, context, false);
365 + handleTopLevelType(topLevelType, node, responderEvent, false);
366 }
367 node = node.return;
368 }
@@ -399,11 +378,11 @@ export function runResponderEventsInBatch(
378 handleTopLevelType(
379 topLevelType,
380 rootEventComponentFiber,
402 - context,
381 + responderEvent,
382 true,
383 );
384 }
385 }
407 - processEventQueue(context._eventQueue);
386 + processEventQueue();
387 }
388 }
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+49 -53
@@ -13,10 +13,10 @@ let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15
16 -function createReactEventComponent(targetEventTypes, handleEvent) {
16 +function createReactEventComponent(targetEventTypes, onEvent) {
17 const testEventResponder = {
18 targetEventTypes,
19 - handleEvent,
19 + onEvent,
20 };
21
22 return {
@@ -53,19 +53,19 @@ describe('DOMEventResponderSystem', () => {
53 container = null;
54 });
55
56 - it('the event responder handleEvent() function should fire on click event', () => {
56 + it('the event responder onEvent() function should fire on click event', () => {
57 let eventResponderFiredCount = 0;
58 let eventLog = [];
59 const buttonRef = React.createRef();
60
61 const ClickEventComponent = createReactEventComponent(
62 ['click'],
63 - (context, props) => {
63 + (event, context, props) => {
64 eventResponderFiredCount++;
65 eventLog.push({
66 - name: context.eventType,
67 - passive: context.isPassive(),
68 - passiveSupported: context.isPassiveSupported(),
66 + name: event.type,
67 + passive: event.passive,
68 + passiveSupported: event.passiveSupported,
69 });
70 },
71 );
@@ -79,7 +79,7 @@ describe('DOMEventResponderSystem', () => {
79 ReactDOM.render(<Test />, container);
80 expect(container.innerHTML).toBe('<button>Click me!</button>');
81
82 - // Clicking the button should trigger the event responder handleEvent()
82 + // Clicking the button should trigger the event responder onEvent()
83 let buttonElement = buttonRef.current;
84 dispatchClickEvent(buttonElement);
85 expect(eventResponderFiredCount).toBe(1);
@@ -103,7 +103,7 @@ describe('DOMEventResponderSystem', () => {
103 expect(eventResponderFiredCount).toBe(2);
104 });
105
106 - it('the event responder handleEvent() function should fire on click event (passive events forced)', () => {
106 + it('the event responder onEvent() function should fire on click event (passive events forced)', () => {
107 // JSDOM does not support passive events, so this manually overrides the value to be true
108 const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
109 checkPassiveEvents.passiveBrowserEventsSupported = true;
@@ -113,11 +113,11 @@ describe('DOMEventResponderSystem', () => {
113
114 const ClickEventComponent = createReactEventComponent(
115 ['click'],
116 - (context, props) => {
116 + (event, context, props) => {
117 eventLog.push({
118 - name: context.eventType,
119 - passive: context.isPassive(),
120 - passiveSupported: context.isPassiveSupported(),
118 + name: event.type,
119 + passive: event.passive,
120 + passiveSupported: event.passiveSupported,
121 });
122 },
123 );
@@ -130,7 +130,7 @@ describe('DOMEventResponderSystem', () => {
130
131 ReactDOM.render(<Test />, container);
132
133 - // Clicking the button should trigger the event responder handleEvent()
133 + // Clicking the button should trigger the event responder onEvent()
134 let buttonElement = buttonRef.current;
135 dispatchClickEvent(buttonElement);
136 expect(eventLog.length).toBe(1);
@@ -141,19 +141,19 @@ describe('DOMEventResponderSystem', () => {
141 });
142 });
143
144 - it('nested event responders and their handleEvent() function should fire multiple times', () => {
144 + it('nested event responders and their onEvent() function should fire multiple times', () => {
145 let eventResponderFiredCount = 0;
146 let eventLog = [];
147 const buttonRef = React.createRef();
148
149 const ClickEventComponent = createReactEventComponent(
150 ['click'],
151 - (context, props) => {
151 + (event, context, props) => {
152 eventResponderFiredCount++;
153 eventLog.push({
154 - name: context.eventType,
155 - passive: context.isPassive(),
156 - passiveSupported: context.isPassiveSupported(),
154 + name: event.type,
155 + passive: event.passive,
156 + passiveSupported: event.passiveSupported,
157 });
158 },
159 );
@@ -168,7 +168,7 @@ describe('DOMEventResponderSystem', () => {
168
169 ReactDOM.render(<Test />, container);
170
171 - // Clicking the button should trigger the event responder handleEvent()
171 + // Clicking the button should trigger the event responder onEvent()
172 let buttonElement = buttonRef.current;
173 dispatchClickEvent(buttonElement);
174 expect(eventResponderFiredCount).toBe(2);
@@ -186,7 +186,7 @@ describe('DOMEventResponderSystem', () => {
186 });
187 });
188
189 - it('nested event responders and their handleEvent() should fire in the correct order', () => {
189 + it('nested event responders and their onEvent() should fire in the correct order', () => {
190 let eventLog = [];
191 const buttonRef = React.createRef();
192
@@ -214,7 +214,7 @@ describe('DOMEventResponderSystem', () => {
214
215 ReactDOM.render(<Test />, container);
216
217 - // Clicking the button should trigger the event responder handleEvent()
217 + // Clicking the button should trigger the event responder onEvent()
218 let buttonElement = buttonRef.current;
219 dispatchClickEvent(buttonElement);
220
@@ -227,14 +227,14 @@ describe('DOMEventResponderSystem', () => {
227
228 const ClickEventComponent = createReactEventComponent(
229 ['click'],
230 - (context, props) => {
230 + (event, context, props) => {
231 if (props.onMagicClick) {
232 - const event = {
232 + const syntheticEvent = {
233 listener: props.onMagicClick,
234 - target: context.eventTarget,
234 + target: event.target,
235 type: 'magicclick',
236 };
237 - context.dispatchEvent(event, {discrete: true});
237 + context.dispatchEvent(syntheticEvent, {discrete: true});
238 }
239 },
240 );
@@ -251,7 +251,7 @@ describe('DOMEventResponderSystem', () => {
251
252 ReactDOM.render(<Test />, container);
253
254 - // Clicking the button should trigger the event responder handleEvent()
254 + // Clicking the button should trigger the event responder onEvent()
255 let buttonElement = buttonRef.current;
256 dispatchClickEvent(buttonElement);
257
@@ -264,37 +264,33 @@ describe('DOMEventResponderSystem', () => {
264
265 const LongPressEventComponent = createReactEventComponent(
266 ['click'],
267 - (context, props) => {
267 + (event, context, props) => {
268 const pressEvent = {
269 listener: props.onPress,
270 - target: context.eventTarget,
270 + target: event.target,
271 type: 'press',
272 };
273 context.dispatchEvent(pressEvent, {discrete: true});
274
275 - setTimeout(
276 - () =>
277 - context.withAsyncDispatching(() => {
278 - if (props.onLongPress) {
279 - const longPressEvent = {
280 - listener: props.onLongPress,
281 - target: context.eventTarget,
282 - type: 'longpress',
283 - };
284 - context.dispatchEvent(longPressEvent, {discrete: true});
285 - }
286 -
287 - if (props.onLongPressChange) {
288 - const longPressChangeEvent = {
289 - listener: props.onLongPressChange,
290 - target: context.eventTarget,
291 - type: 'longpresschange',
292 - };
293 - context.dispatchEvent(longPressChangeEvent, {discrete: true});
294 - }
295 - }),
296 - 500,
297 - );
275 + context.setTimeout(() => {
276 + if (props.onLongPress) {
277 + const longPressEvent = {
278 + listener: props.onLongPress,
279 + target: event.target,
280 + type: 'longpress',
281 + };
282 + context.dispatchEvent(longPressEvent, {discrete: true});
283 + }
284 +
285 + if (props.onLongPressChange) {
286 + const longPressChangeEvent = {
287 + listener: props.onLongPressChange,
288 + target: event.target,
289 + type: 'longpresschange',
290 + };
291 + context.dispatchEvent(longPressChangeEvent, {discrete: true});
292 + }
293 + }, 500);
294 },
295 );
296
@@ -313,7 +309,7 @@ describe('DOMEventResponderSystem', () => {
309
310 ReactDOM.render(<Test />, container);
311
316 - // Clicking the button should trigger the event responder handleEvent()
312 + // Clicking the button should trigger the event responder onEvent()
313 let buttonElement = buttonRef.current;
314 dispatchClickEvent(buttonElement);
315 jest.runAllTimers();
packages/react-events/src/Drag.js
+20 -15
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {EventResponderContext} from 'events/EventTypes';
10 +import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
11 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12
13 const targetEventTypes = ['pointerdown', 'pointercancel'];
@@ -62,7 +62,7 @@ function createDragEvent(
62 }
63
64 function dispatchDragEvent(
65 - context: EventResponderContext,
65 + context: ResponderContext,
66 name: DragEventType,
67 listener: DragEvent => void,
68 state: DragState,
@@ -87,28 +87,31 @@ const DragResponder = {
87 y: 0,
88 };
89 },
90 - handleEvent(
91 - context: EventResponderContext,
90 + onEvent(
91 + event: ResponderEvent,
92 + context: ResponderContext,
93 props: Object,
94 state: DragState,
95 ): void {
95 - const {eventTarget, eventType, event} = context;
96 + const {target, type, nativeEvent} = event;
97
97 - switch (eventType) {
98 + switch (type) {
99 case 'touchstart':
100 case 'mousedown':
101 case 'pointerdown': {
102 if (!state.isDragging) {
103 if (props.onShouldClaimOwnership) {
103 - context.releaseOwnership(state.dragTarget);
104 + context.releaseOwnership();
105 }
106 const obj =
106 - eventType === 'touchstart' ? (event: any).changedTouches[0] : event;
107 + type === 'touchstart'
108 + ? (nativeEvent: any).changedTouches[0]
109 + : nativeEvent;
110 const x = (state.startX = (obj: any).screenX);
111 const y = (state.startY = (obj: any).screenY);
112 state.x = x;
113 state.y = y;
111 - state.dragTarget = eventTarget;
114 + state.dragTarget = target;
115 state.isPointerDown = true;
116
117 if (props.onDragStart) {
@@ -121,19 +124,21 @@ const DragResponder = {
124 );
125 }
126
124 - context.addRootEventTypes(rootEventTypes);
127 + context.addRootEventTypes(target.ownerDocument, rootEventTypes);
128 }
129 break;
130 }
131 case 'touchmove':
132 case 'mousemove':
133 case 'pointermove': {
131 - if (context.isPassive()) {
134 + if (event.passive) {
135 return;
136 }
137 if (state.isPointerDown) {
138 const obj =
136 - eventType === 'touchmove' ? (event: any).changedTouches[0] : event;
139 + type === 'touchmove'
140 + ? (nativeEvent: any).changedTouches[0]
141 + : nativeEvent;
142 const x = (obj: any).screenX;
143 const y = (obj: any).screenY;
144 state.x = x;
@@ -145,7 +150,7 @@ const DragResponder = {
150 props.onShouldClaimOwnership &&
151 props.onShouldClaimOwnership()
152 ) {
148 - shouldEnableDragging = context.requestOwnership(state.dragTarget);
153 + shouldEnableDragging = context.requestOwnership();
154 }
155 if (shouldEnableDragging) {
156 state.isDragging = true;
@@ -181,7 +186,7 @@ const DragResponder = {
186 eventData,
187 );
188 }
184 - (event: any).preventDefault();
189 + (nativeEvent: any).preventDefault();
190 }
191 }
192 break;
@@ -193,7 +198,7 @@ const DragResponder = {
198 case 'pointerup': {
199 if (state.isDragging) {
200 if (props.onShouldClaimOwnership) {
196 - context.releaseOwnership(state.dragTarget);
201 + context.releaseOwnership();
202 }
203 if (props.onDragEnd) {
204 dispatchDragEvent(context, 'dragend', props.onDragEnd, state, true);
packages/react-events/src/Focus.js
+21 -30
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {EventResponderContext} from 'events/EventTypes';
10 +import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
11 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12
13 type FocusProps = {
@@ -47,55 +47,45 @@ function createFocusEvent(
47 }
48
49 function dispatchFocusInEvents(
50 - context: EventResponderContext,
50 + event: ResponderEvent,
51 + context: ResponderContext,
52 props: FocusProps,
53 ) {
53 - const {event, eventTarget} = context;
54 - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
54 + const {nativeEvent, target} = event;
55 + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
56 return;
57 }
58 if (props.onFocus) {
58 - const syntheticEvent = createFocusEvent(
59 - 'focus',
60 - eventTarget,
61 - props.onFocus,
62 - );
59 + const syntheticEvent = createFocusEvent('focus', target, props.onFocus);
60 context.dispatchEvent(syntheticEvent, {discrete: true});
61 }
62 if (props.onFocusChange) {
63 const listener = () => {
64 props.onFocusChange(true);
65 };
69 - const syntheticEvent = createFocusEvent(
70 - 'focuschange',
71 - eventTarget,
72 - listener,
73 - );
66 + const syntheticEvent = createFocusEvent('focuschange', target, listener);
67 context.dispatchEvent(syntheticEvent, {discrete: true});
68 }
69 }
70
71 function dispatchFocusOutEvents(
79 - context: EventResponderContext,
72 + event: ResponderEvent,
73 + context: ResponderContext,
74 props: FocusProps,
75 ) {
82 - const {event, eventTarget} = context;
83 - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
76 + const {nativeEvent, target} = event;
77 + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
78 return;
79 }
80 if (props.onBlur) {
87 - const syntheticEvent = createFocusEvent('blur', eventTarget, props.onBlur);
81 + const syntheticEvent = createFocusEvent('blur', target, props.onBlur);
82 context.dispatchEvent(syntheticEvent, {discrete: true});
83 }
84 if (props.onFocusChange) {
85 const listener = () => {
86 props.onFocusChange(false);
87 };
94 - const syntheticEvent = createFocusEvent(
95 - 'focuschange',
96 - eventTarget,
97 - listener,
98 - );
88 + const syntheticEvent = createFocusEvent('focuschange', target, listener);
89 context.dispatchEvent(syntheticEvent, {discrete: true});
90 }
91 }
@@ -107,24 +97,25 @@ const FocusResponder = {
97 isFocused: false,
98 };
99 },
110 - handleEvent(
111 - context: EventResponderContext,
100 + onEvent(
101 + event: ResponderEvent,
102 + context: ResponderContext,
103 props: Object,
104 state: FocusState,
105 ): void {
115 - const {eventTarget, eventType} = context;
106 + const {type} = event;
107
117 - switch (eventType) {
108 + switch (type) {
109 case 'focus': {
119 - if (!state.isFocused && !context.isTargetOwned(eventTarget)) {
120 - dispatchFocusInEvents(context, props);
110 + if (!state.isFocused && !context.hasOwnership()) {
111 + dispatchFocusInEvents(event, context, props);
112 state.isFocused = true;
113 }
114 break;
115 }
116 case 'blur': {
117 if (state.isFocused) {
127 - dispatchFocusOutEvents(context, props);
118 + dispatchFocusOutEvents(event, context, props);
119 state.isFocused = false;
120 }
121 break;
packages/react-events/src/Hover.js
+31 -40
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {EventResponderContext} from 'events/EventTypes';
10 +import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
11 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12
13 type HoverProps = {
@@ -61,17 +61,18 @@ function createHoverEvent(
61 }
62
63 function dispatchHoverStartEvents(
64 - context: EventResponderContext,
64 + event: ResponderEvent,
65 + context: ResponderContext,
66 props: HoverProps,
67 ): void {
67 - const {event, eventTarget} = context;
68 - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
68 + const {nativeEvent, target} = event;
69 + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
70 return;
71 }
72 if (props.onHoverStart) {
73 const syntheticEvent = createHoverEvent(
74 'hoverstart',
74 - eventTarget,
75 + target,
76 props.onHoverStart,
77 );
78 context.dispatchEvent(syntheticEvent, {discrete: true});
@@ -80,27 +81,24 @@ function dispatchHoverStartEvents(
81 const listener = () => {
82 props.onHoverChange(true);
83 };
83 - const syntheticEvent = createHoverEvent(
84 - 'hoverchange',
85 - eventTarget,
86 - listener,
87 - );
84 + const syntheticEvent = createHoverEvent('hoverchange', target, listener);
85 context.dispatchEvent(syntheticEvent, {discrete: true});
86 }
87 }
88
89 function dispatchHoverEndEvents(
93 - context: EventResponderContext,
90 + event: ResponderEvent,
91 + context: ResponderContext,
92 props: HoverProps,
93 ) {
96 - const {event, eventTarget} = context;
97 - if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
94 + const {nativeEvent, target} = event;
95 + if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
96 return;
97 }
98 if (props.onHoverEnd) {
99 const syntheticEvent = createHoverEvent(
100 'hoverend',
103 - eventTarget,
101 + target,
102 props.onHoverEnd,
103 );
104 context.dispatchEvent(syntheticEvent, {discrete: true});
@@ -109,11 +107,7 @@ function dispatchHoverEndEvents(
107 const listener = () => {
108 props.onHoverChange(false);
109 };
112 - const syntheticEvent = createHoverEvent(
113 - 'hoverchange',
114 - eventTarget,
115 - listener,
116 - );
110 + const syntheticEvent = createHoverEvent('hoverchange', target, listener);
111 context.dispatchEvent(syntheticEvent, {discrete: true});
112 }
113 }
@@ -127,14 +121,15 @@ const HoverResponder = {
121 isTouched: false,
122 };
123 },
130 - handleEvent(
131 - context: EventResponderContext,
124 + onEvent(
125 + event: ResponderEvent,
126 + context: ResponderContext,
127 props: HoverProps,
128 state: HoverState,
129 ): void {
135 - const {eventType, eventTarget, event} = context;
130 + const {type, nativeEvent} = event;
131
137 - switch (eventType) {
132 + switch (type) {
133 /**
134 * Prevent hover events when touch is being used.
135 */
@@ -147,25 +142,21 @@ const HoverResponder = {
142
143 case 'pointerover':
144 case 'mouseover': {
150 - if (
151 - !state.isHovered &&
152 - !state.isTouched &&
153 - !context.isTargetOwned(eventTarget)
154 - ) {
155 - if ((event: any).pointerType === 'touch') {
145 + if (!state.isHovered && !state.isTouched && !context.hasOwnership()) {
146 + if ((nativeEvent: any).pointerType === 'touch') {
147 state.isTouched = true;
148 return;
149 }
150 if (
151 context.isPositionWithinTouchHitTarget(
161 - (event: any).x,
162 - (event: any).y,
152 + (nativeEvent: any).x,
153 + (nativeEvent: any).y,
154 )
155 ) {
156 state.isInHitSlop = true;
157 return;
158 }
168 - dispatchHoverStartEvents(context, props);
159 + dispatchHoverStartEvents(event, context, props);
160 state.isHovered = true;
161 }
162 break;
@@ -173,7 +164,7 @@ const HoverResponder = {
164 case 'pointerout':
165 case 'mouseout': {
166 if (state.isHovered && !state.isTouched) {
176 - dispatchHoverEndEvents(context, props);
167 + dispatchHoverEndEvents(event, context, props);
168 state.isHovered = false;
169 }
170 state.isInHitSlop = false;
@@ -185,22 +176,22 @@ const HoverResponder = {
176 if (state.isInHitSlop) {
177 if (
178 !context.isPositionWithinTouchHitTarget(
188 - (event: any).x,
189 - (event: any).y,
179 + (nativeEvent: any).x,
180 + (nativeEvent: any).y,
181 )
182 ) {
192 - dispatchHoverStartEvents(context, props);
183 + dispatchHoverStartEvents(event, context, props);
184 state.isHovered = true;
185 state.isInHitSlop = false;
186 }
187 } else if (
188 state.isHovered &&
189 context.isPositionWithinTouchHitTarget(
199 - (event: any).x,
200 - (event: any).y,
190 + (nativeEvent: any).x,
191 + (nativeEvent: any).y,
192 )
193 ) {
203 - dispatchHoverEndEvents(context, props);
194 + dispatchHoverEndEvents(event, context, props);
195 state.isHovered = false;
196 state.isInHitSlop = true;
197 }
@@ -209,7 +200,7 @@ const HoverResponder = {
200 }
201 case 'pointercancel': {
202 if (state.isHovered && !state.isTouched) {
212 - dispatchHoverEndEvents(context, props);
203 + dispatchHoverEndEvents(event, context, props);
204 state.isHovered = false;
205 state.isTouched = false;
206 }
packages/react-events/src/Press.js
+84 -60
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {EventResponderContext} from 'events/EventTypes';
10 +import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
11 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12
13 type PressProps = {
@@ -86,7 +86,7 @@ function createPressEvent(
86 }
87
88 function dispatchEvent(
89 - context: EventResponderContext,
89 + context: ResponderContext,
90 state: PressState,
91 name: PressEventType,
92 listener: (e: Object) => void,
@@ -97,7 +97,7 @@ function dispatchEvent(
97 }
98
99 function dispatchPressChangeEvent(
100 - context: EventResponderContext,
100 + context: ResponderContext,
101 props: PressProps,
102 state: PressState,
103 ): void {
@@ -108,7 +108,7 @@ function dispatchPressChangeEvent(
108 }
109
110 function dispatchLongPressChangeEvent(
111 - context: EventResponderContext,
111 + context: ResponderContext,
112 props: PressProps,
113 state: PressState,
114 ): void {
@@ -119,7 +119,7 @@ function dispatchLongPressChangeEvent(
119 }
120
121 function dispatchPressStartEvents(
122 - context: EventResponderContext,
122 + context: ResponderContext,
123 props: PressProps,
124 state: PressState,
125 ): void {
@@ -138,34 +138,30 @@ function dispatchPressStartEvents(
138 DEFAULT_LONG_PRESS_DELAY_MS,
139 );
140
141 - state.longPressTimeout = setTimeout(
142 - () =>
143 - context.withAsyncDispatching(() => {
144 - state.isLongPressed = true;
145 - state.longPressTimeout = null;
146 -
147 - if (props.onLongPress) {
148 - const listener = e => {
149 - props.onLongPress(e);
150 - // TODO address this again at some point
151 - // if (e.nativeEvent.defaultPrevented) {
152 - // state.defaultPrevented = true;
153 - // }
154 - };
155 - dispatchEvent(context, state, 'longpress', listener);
156 - }
141 + state.longPressTimeout = context.setTimeout(() => {
142 + state.isLongPressed = true;
143 + state.longPressTimeout = null;
144 +
145 + if (props.onLongPress) {
146 + const listener = e => {
147 + props.onLongPress(e);
148 + // TODO address this again at some point
149 + // if (e.nativeEvent.defaultPrevented) {
150 + // state.defaultPrevented = true;
151 + // }
152 + };
153 + dispatchEvent(context, state, 'longpress', listener);
154 + }
155
158 - if (props.onLongPressChange) {
159 - dispatchLongPressChangeEvent(context, props, state);
160 - }
161 - }),
162 - delayLongPress,
163 - );
156 + if (props.onLongPressChange) {
157 + dispatchLongPressChangeEvent(context, props, state);
158 + }
159 + }, delayLongPress);
160 }
161 }
162
163 function dispatchPressEndEvents(
168 - context: EventResponderContext,
164 + context: ResponderContext,
165 props: PressProps,
166 state: PressState,
167 ): void {
@@ -206,6 +202,21 @@ function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
202 return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
203 }
204
205 +function unmountResponder(
206 + context: ResponderContext,
207 + props: PressProps,
208 + state: PressState,
209 +): void {
210 + if (state.isPressed) {
211 + state.isPressed = false;
212 + dispatchPressEndEvents(context, props, state);
213 + if (state.longPressTimeout !== null) {
214 + clearTimeout(state.longPressTimeout);
215 + state.longPressTimeout = null;
216 + }
217 + }
218 +}
219 +
220 const PressResponder = {
221 targetEventTypes,
222 createInitialState(): PressState {
@@ -219,14 +230,15 @@ const PressResponder = {
230 shouldSkipMouseAfterTouch: false,
231 };
232 },
222 - handleEvent(
223 - context: EventResponderContext,
233 + onEvent(
234 + event: ResponderEvent,
235 + context: ResponderContext,
236 props: PressProps,
237 state: PressState,
238 ): void {
227 - const {eventTarget, eventType, event} = context;
239 + const {target, type, nativeEvent} = event;
240
229 - switch (eventType) {
241 + switch (type) {
242 /**
243 * Respond to pointer events and fall back to mouse.
244 */
@@ -234,29 +246,29 @@ const PressResponder = {
246 case 'mousedown': {
247 if (
248 !state.isPressed &&
237 - !context.isTargetOwned(eventTarget) &&
249 + !context.hasOwnership() &&
250 !state.shouldSkipMouseAfterTouch
251 ) {
252 if (
241 - (event: any).pointerType === 'mouse' ||
242 - eventType === 'mousedown'
253 + (nativeEvent: any).pointerType === 'mouse' ||
254 + type === 'mousedown'
255 ) {
256 if (
257 // Ignore right- and middle-clicks
246 - event.button === 1 ||
247 - event.button === 2 ||
258 + nativeEvent.button === 1 ||
259 + nativeEvent.button === 2 ||
260 // Ignore pressing on hit slop area with mouse
261 context.isPositionWithinTouchHitTarget(
250 - (event: any).x,
251 - (event: any).y,
262 + (nativeEvent: any).x,
263 + (nativeEvent: any).y,
264 )
265 ) {
266 return;
267 }
268 }
257 - state.pressTarget = eventTarget;
269 + state.pressTarget = target;
270 dispatchPressStartEvents(context, props, state);
259 - context.addRootEventTypes(rootEventTypes);
271 + context.addRootEventTypes(target.ownerDocument, rootEventTypes);
272 }
273 break;
274 }
@@ -273,7 +285,7 @@ const PressResponder = {
285 dispatchPressEndEvents(context, props, state);
286
287 if (state.pressTarget !== null && props.onPress) {
276 - if (context.isTargetWithinElement(eventTarget, state.pressTarget)) {
288 + if (context.isTargetWithinElement(target, state.pressTarget)) {
289 if (
290 !(
291 wasLongPressed &&
@@ -303,16 +315,16 @@ const PressResponder = {
315 * support for pointer events.
316 */
317 case 'touchstart': {
306 - if (!state.isPressed && !context.isTargetOwned(eventTarget)) {
318 + if (!state.isPressed && !context.hasOwnership()) {
319 // We bail out of polyfilling anchor tags, given the same heuristics
320 // explained above in regards to needing to use click events.
309 - if (isAnchorTagElement(eventTarget)) {
321 + if (isAnchorTagElement(target)) {
322 state.isAnchorTouched = true;
323 return;
324 }
313 - state.pressTarget = eventTarget;
325 + state.pressTarget = target;
326 dispatchPressStartEvents(context, props, state);
315 - context.addRootEventTypes(rootEventTypes);
327 + context.addRootEventTypes(target.ownerDocument, rootEventTypes);
328 }
329 break;
330 }
@@ -326,17 +338,17 @@ const PressResponder = {
338
339 dispatchPressEndEvents(context, props, state);
340
329 - if (eventType !== 'touchcancel' && props.onPress) {
341 + if (type !== 'touchcancel' && props.onPress) {
342 // Find if the X/Y of the end touch is still that of the original target
331 - const changedTouch = (event: any).changedTouches[0];
332 - const doc = (eventTarget: any).ownerDocument;
333 - const target = doc.elementFromPoint(
343 + const changedTouch = (nativeEvent: any).changedTouches[0];
344 + const doc = (target: any).ownerDocument;
345 + const fromTarget = doc.elementFromPoint(
346 changedTouch.screenX,
347 changedTouch.screenY,
348 );
349 if (
338 - target !== null &&
339 - context.isTargetWithinEventComponent(target)
350 + fromTarget !== null &&
351 + context.isTargetWithinEventComponent(fromTarget)
352 ) {
353 if (
354 !(
@@ -363,21 +375,21 @@ const PressResponder = {
375 if (
376 !state.isPressed &&
377 !state.isLongPressed &&
366 - !context.isTargetOwned(eventTarget) &&
367 - isValidKeyPress((event: any).key)
378 + !context.hasOwnership() &&
379 + isValidKeyPress((nativeEvent: any).key)
380 ) {
381 // Prevent spacebar press from scrolling the window
370 - if ((event: any).key === ' ') {
371 - (event: any).preventDefault();
382 + if ((nativeEvent: any).key === ' ') {
383 + (nativeEvent: any).preventDefault();
384 }
373 - state.pressTarget = eventTarget;
385 + state.pressTarget = target;
386 dispatchPressStartEvents(context, props, state);
375 - context.addRootEventTypes(rootEventTypes);
387 + context.addRootEventTypes(target.ownerDocument, rootEventTypes);
388 }
389 break;
390 }
391 case 'keyup': {
380 - if (state.isPressed && isValidKeyPress((event: any).key)) {
392 + if (state.isPressed && isValidKeyPress((nativeEvent: any).key)) {
393 const wasLongPressed = state.isLongPressed;
394 dispatchPressEndEvents(context, props, state);
395 if (state.pressTarget !== null && props.onPress) {
@@ -410,12 +422,24 @@ const PressResponder = {
422
423 case 'click': {
424 if (state.defaultPrevented) {
413 - (event: any).preventDefault();
425 + (nativeEvent: any).preventDefault();
426 state.defaultPrevented = false;
427 }
428 }
429 }
430 },
431 + // TODO This method doesn't work as of yet
432 + onUnmount(context: ResponderContext, props: PressProps, state: PressState) {
433 + unmountResponder(context, props, state);
434 + },
435 + // TODO This method doesn't work as of yet
436 + onOwnershipChange(
437 + context: ResponderContext,
438 + props: PressProps,
439 + state: PressState,
440 + ) {
441 + unmountResponder(context, props, state);
442 + },
443 };
444
445 export default {
packages/react-events/src/Swipe.js
+19 -18
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {EventResponderContext} from 'events/EventTypes';
10 +import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
11 import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12
13 const targetEventTypes = ['pointerdown', 'pointercancel'];
@@ -52,7 +52,7 @@ function createSwipeEvent(
52 }
53
54 function dispatchSwipeEvent(
55 - context: EventResponderContext,
55 + context: ResponderContext,
56 name: SwipeEventType,
57 listener: SwipeEvent => void,
58 state: SwipeState,
@@ -91,21 +91,22 @@ const SwipeResponder = {
91 y: 0,
92 };
93 },
94 - handleEvent(
95 - context: EventResponderContext,
94 + onEvent(
95 + event: ResponderEvent,
96 + context: ResponderContext,
97 props: Object,
98 state: SwipeState,
99 ): void {
99 - const {eventTarget, eventType, event} = context;
100 + const {target, type, nativeEvent} = event;
101
101 - switch (eventType) {
102 + switch (type) {
103 case 'touchstart':
104 case 'mousedown':
105 case 'pointerdown': {
105 - if (!state.isSwiping && !context.isTargetOwned(eventTarget)) {
106 + if (!state.isSwiping && !context.hasOwnership()) {
107 let obj = event;
107 - if (eventType === 'touchstart') {
108 - obj = (event: any).targetTouches[0];
108 + if (type === 'touchstart') {
109 + obj = (nativeEvent: any).targetTouches[0];
110 state.touchId = obj.identifier;
111 }
112 const x = (obj: any).screenX;
@@ -114,7 +115,7 @@ const SwipeResponder = {
115 let shouldEnableSwiping = true;
116
117 if (props.onShouldClaimOwnership && props.onShouldClaimOwnership()) {
117 - shouldEnableSwiping = context.requestOwnership(eventTarget);
118 + shouldEnableSwiping = context.requestOwnership();
119 }
120 if (shouldEnableSwiping) {
121 state.isSwiping = true;
@@ -122,8 +123,8 @@ const SwipeResponder = {
123 state.startY = y;
124 state.x = x;
125 state.y = y;
125 - state.swipeTarget = eventTarget;
126 - context.addRootEventTypes(rootEventTypes);
126 + state.swipeTarget = target;
127 + context.addRootEventTypes(target.ownerDocument, rootEventTypes);
128 } else {
129 state.touchId = null;
130 }
@@ -133,13 +134,13 @@ const SwipeResponder = {
134 case 'touchmove':
135 case 'mousemove':
136 case 'pointermove': {
136 - if (context.isPassive()) {
137 + if (event.passive) {
138 return;
139 }
140 if (state.isSwiping) {
141 let obj = null;
141 - if (eventType === 'touchmove') {
142 - const targetTouches = (event: any).targetTouches;
142 + if (type === 'touchmove') {
143 + const targetTouches = (nativeEvent: any).targetTouches;
144 for (let i = 0; i < targetTouches.length; i++) {
145 if (state.touchId === targetTouches[i].identifier) {
146 obj = targetTouches[i];
@@ -147,7 +148,7 @@ const SwipeResponder = {
148 }
149 }
150 } else {
150 - obj = event;
151 + obj = nativeEvent;
152 }
153 if (obj === null) {
154 state.isSwiping = false;
@@ -178,7 +179,7 @@ const SwipeResponder = {
179 false,
180 eventData,
181 );
181 - (event: any).preventDefault();
182 + (nativeEvent: any).preventDefault();
183 }
184 }
185 break;
@@ -193,7 +194,7 @@ const SwipeResponder = {
194 return;
195 }
196 if (props.onShouldClaimOwnership) {
196 - context.releaseOwnership(state.swipeTarget);
197 + context.releaseOwnership();
198 }
199 const direction = state.direction;
200 const lastDirection = state.lastDirection;
packages/react-events/src/__tests__/TouchHitTarget-test.internal.js
+1 -1
@@ -22,7 +22,7 @@ let TouchHitTarget;
22
23 const noOpResponder = {
24 targetEventTypes: [],
25 - handleEvent() {},
25 + onEvent() {},
26 };
27
28 function createReactEventComponent() {
packages/react-reconciler/src/ReactFiber.js
+1
@@ -624,6 +624,7 @@ export function createFiberFromEventComponent(
624 fiber.elementType = eventComponent;
625 fiber.type = eventComponent;
626 fiber.stateNode = {
627 + context: null,
628 props: pendingProps,
629 state: null,
630 };
packages/shared/ReactTypes.js
+14 -1
@@ -7,6 +7,8 @@
7 * @flow
8 */
9
10 +import type {ResponderEvent, ResponderContext} from 'events/EventTypes';
11 +
12 export type ReactNode =
13 | React$Element<any>
14 | ReactPortal
@@ -88,7 +90,18 @@ export type ReactEventResponderEventType =
90 export type ReactEventResponder = {
91 targetEventTypes: Array<ReactEventResponderEventType>,
92 createInitialState?: (props: Object) => Object,
91 - handleEvent: (context: Object, props: Object, state: Object) => void,
93 + onEvent: (
94 + event: ResponderEvent,
95 + context: ResponderContext,
96 + props: Object,
97 + state: Object,
98 + ) => void,
99 + onUnmount: (context: ResponderContext, props: Object, state: Object) => void,
100 + onOwnershipChange: (
101 + context: ResponderContext,
102 + props: Object,
103 + state: Object,
104 + ) => void,
105 };
106
107 export type ReactEventComponent = {|