@samitouri / QOS-React-2 / commits / 5098891193

[Flare] Redesign core event system (#16163)

Dominic Gannaway committed Jul 23, 2019 at 23:46 UTC 509889119360ed83ca6ef3f83bcf01e5aa7dcd81
59 files changed +3418 -4039
packages/events/ReactGenericBatching.js
+3 -3
@@ -59,15 +59,15 @@ export function batchedUpdates(fn, bookkeeping) {
59 }
60 }
61
62 -export function batchedEventUpdates(fn, bookkeeping) {
62 +export function batchedEventUpdates(fn, a, b) {
63 if (isInsideEventHandler) {
64 // If we are currently inside another batch, we need to wait until it
65 // fully completes before restoring state.
66 - return fn(bookkeeping);
66 + return fn(a, b);
67 }
68 isInsideEventHandler = true;
69 try {
70 - return batchedEventUpdatesImpl(fn, bookkeeping);
70 + return batchedEventUpdatesImpl(fn, a, b);
71 } finally {
72 isInsideEventHandler = false;
73 finishEventHandler();
packages/react-art/src/ReactARTHostConfig.js
+13 -15
@@ -10,7 +10,10 @@ import Mode from 'art/modes/current';
10 import invariant from 'shared/invariant';
11
12 import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
13 -import type {ReactEventComponentInstance} from 'shared/ReactTypes';
13 +import type {
14 + ReactEventResponder,
15 + ReactEventResponderInstance,
16 +} from 'shared/ReactTypes';
17
18 const pooledTransform = new Transform();
19
@@ -329,10 +332,6 @@ export function getChildHostContext() {
332 return NO_CONTEXT;
333 }
334
332 -export function getChildHostContextForEventComponent() {
333 - return NO_CONTEXT;
334 -}
335 -
335 export const scheduleTimeout = setTimeout;
336 export const cancelTimeout = clearTimeout;
337 export const noTimeout = -1;
@@ -427,20 +426,19 @@ export function unhideTextInstance(textInstance, text): void {
426 // Noop
427 }
428
430 -export function mountEventComponent(
431 - eventComponentInstance: ReactEventComponentInstance<any, any>,
432 -) {
433 - throw new Error('Not yet implemented.');
434 -}
435 -
436 -export function updateEventComponent(
437 - eventComponentInstance: ReactEventComponentInstance<any, any>,
429 +export function mountResponderInstance(
430 + responder: ReactEventResponder<any, any>,
431 + responderInstance: ReactEventResponderInstance<any, any>,
432 + props: Object,
433 + state: Object,
434 + instance: Object,
435 + rootContainerInstance: Object,
436 ) {
437 throw new Error('Not yet implemented.');
438 }
439
442 -export function unmountEventComponent(
443 - eventComponentInstance: ReactEventComponentInstance<any, any>,
440 +export function unmountResponderInstance(
441 + responderInstance: ReactEventResponderInstance<any, any>,
442 ): void {
443 throw new Error('Not yet implemented.');
444 }
packages/react-debug-tools/src/ReactDebugHooks.js
+2 -2
@@ -215,7 +215,7 @@ function useMemo<T>(
215 return value;
216 }
217
218 -function useEvent() {
218 +function useListener() {
219 throw new Error('TODO: not yet implemented');
220 }
221
@@ -231,7 +231,7 @@ const Dispatcher: DispatcherType = {
231 useReducer,
232 useRef,
233 useState,
234 - useEvent,
234 + useListener,
235 };
236
237 // Inspect
packages/react-dom/src/client/ReactDOMComponent.js
+5
@@ -98,6 +98,7 @@ const AUTOFOCUS = 'autoFocus';
98 const CHILDREN = 'children';
99 const STYLE = 'style';
100 const HTML = '__html';
101 +const RESPONDERS = 'responders';
102
103 const {html: HTML_NAMESPACE} = Namespaces;
104
@@ -340,6 +341,7 @@ function setInitialDOMProperties(
341 setTextContent(domElement, '' + nextProp);
342 }
343 } else if (
344 + (enableFlareAPI && propKey === RESPONDERS) ||
345 propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
346 propKey === SUPPRESS_HYDRATION_WARNING
347 ) {
@@ -696,6 +698,7 @@ export function diffProperties(
698 } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
699 // Noop. This is handled by the clear text mechanism.
700 } else if (
701 + (enableFlareAPI && propKey === RESPONDERS) ||
702 propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
703 propKey === SUPPRESS_HYDRATION_WARNING
704 ) {
@@ -787,6 +790,7 @@ export function diffProperties(
790 (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
791 }
792 } else if (
793 + (enableFlareAPI && propKey === RESPONDERS) ||
794 propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
795 propKey === SUPPRESS_HYDRATION_WARNING
796 ) {
@@ -1041,6 +1045,7 @@ export function diffHydratedProperties(
1045 if (suppressHydrationWarning) {
1046 // Don't bother comparing. We're ignoring all these warnings.
1047 } else if (
1048 + (enableFlareAPI && propKey === RESPONDERS) ||
1049 propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
1050 propKey === SUPPRESS_HYDRATION_WARNING ||
1051 // Controlled attributes are not validated
packages/react-dom/src/client/ReactDOMHostConfig.js
+36 -66
@@ -44,11 +44,11 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
44 import type {DOMContainer} from './ReactDOM';
45 import type {
46 ReactDOMEventResponder,
47 - ReactDOMEventComponentInstance,
47 + ReactDOMEventResponderInstance,
48 ReactDOMFundamentalComponentInstance,
49 } from 'shared/ReactDOMTypes';
50 import {
51 - addRootEventTypesForComponentInstance,
51 + addRootEventTypesForResponderInstance,
52 mountEventResponder,
53 unmountEventResponder,
54 } from '../events/DOMEventResponderSystem';
@@ -90,9 +90,6 @@ export type PublicInstance = Element | Text;
90 type HostContextDev = {
91 namespace: string,
92 ancestorInfo: mixed,
93 - eventData: null | {|
94 - isEventComponent?: boolean,
95 - |},
93 };
94 type HostContextProd = string;
95 export type HostContext = HostContextDev | HostContextProd;
@@ -106,7 +103,6 @@ import {
103 enableFlareAPI,
104 enableFundamentalAPI,
105 } from 'shared/ReactFeatureFlags';
109 -import warning from 'shared/warning';
106
107 let SUPPRESS_HYDRATION_WARNING;
108 if (__DEV__) {
@@ -164,7 +160,7 @@ export function getRootHostContext(
160 if (__DEV__) {
161 const validatedTag = type.toLowerCase();
162 const ancestorInfo = updatedAncestorInfo(null, validatedTag);
167 - return {namespace, ancestorInfo, eventData: null};
163 + return {namespace, ancestorInfo};
164 }
165 return namespace;
166 }
@@ -181,26 +177,12 @@ export function getChildHostContext(
177 parentHostContextDev.ancestorInfo,
178 type,
179 );
184 - return {namespace, ancestorInfo, eventData: null};
180 + return {namespace, ancestorInfo};
181 }
182 const parentNamespace = ((parentHostContext: any): HostContextProd);
183 return getChildNamespace(parentNamespace, type);
184 }
185
190 -export function getChildHostContextForEventComponent(
191 - parentHostContext: HostContext,
192 -): HostContext {
193 - if (__DEV__) {
194 - const parentHostContextDev = ((parentHostContext: any): HostContextDev);
195 - const {namespace, ancestorInfo} = parentHostContextDev;
196 - const eventData = {
197 - isEventComponent: true,
198 - };
199 - return {namespace, ancestorInfo, eventData};
200 - }
201 - return parentHostContext;
202 -}
203 -
186 export function getPublicInstance(instance: Instance): * {
187 return instance;
188 }
@@ -332,17 +314,6 @@ export function createTextInstance(
314 if (__DEV__) {
315 const hostContextDev = ((hostContext: any): HostContextDev);
316 validateDOMNesting(null, text, hostContextDev.ancestorInfo);
335 - if (enableFlareAPI) {
336 - const eventData = hostContextDev.eventData;
337 - if (eventData !== null) {
338 - warning(
339 - !eventData.isEventComponent,
340 - 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
341 - 'Wrap the child text "%s" in an element.',
342 - text,
343 - );
344 - }
345 - }
317 }
318 const textNode: TextInstance = createTextNode(text, rootContainerInstance);
319 precacheFiberNode(internalInstanceHandle, textNode);
@@ -844,44 +815,43 @@ export function didNotFindHydratableSuspenseInstance(
815 }
816 }
817
847 -export function mountEventComponent(
848 - eventComponentInstance: ReactDOMEventComponentInstance,
849 -): void {
850 - if (enableFlareAPI) {
851 - const rootContainerInstance = ((eventComponentInstance.rootInstance: any): Container);
852 - const doc = rootContainerInstance.ownerDocument;
853 - const documentBody = doc.body || doc;
854 - const responder = eventComponentInstance.responder;
855 - const {
856 - rootEventTypes,
857 - targetEventTypes,
858 - } = ((responder: any): ReactDOMEventResponder);
859 - if (targetEventTypes !== undefined) {
860 - listenToEventResponderEventTypes(targetEventTypes, documentBody);
861 - }
862 - if (rootEventTypes !== undefined) {
863 - addRootEventTypesForComponentInstance(
864 - eventComponentInstance,
865 - rootEventTypes,
866 - );
867 - listenToEventResponderEventTypes(rootEventTypes, documentBody);
868 - }
869 - mountEventResponder(eventComponentInstance);
870 - }
871 -}
872 -
873 -export function updateEventComponent(
874 - eventComponentInstance: ReactDOMEventComponentInstance,
875 -): void {
876 - // NO-OP, why might use this in the future
818 +export function mountResponderInstance(
819 + responder: ReactDOMEventResponder,
820 + responderInstance: ReactDOMEventResponderInstance,
821 + responderProps: Object,
822 + responderState: Object,
823 + instance: Instance,
824 + rootContainerInstance: Container,
825 +): ReactDOMEventResponderInstance {
826 + // Listen to events
827 + const doc = rootContainerInstance.ownerDocument;
828 + const documentBody = doc.body || doc;
829 + const {
830 + rootEventTypes,
831 + targetEventTypes,
832 + } = ((responder: any): ReactDOMEventResponder);
833 + if (targetEventTypes !== null) {
834 + listenToEventResponderEventTypes(targetEventTypes, documentBody);
835 + }
836 + if (rootEventTypes !== null) {
837 + addRootEventTypesForResponderInstance(responderInstance, rootEventTypes);
838 + listenToEventResponderEventTypes(rootEventTypes, documentBody);
839 + }
840 + mountEventResponder(
841 + responder,
842 + responderInstance,
843 + responderProps,
844 + responderState,
845 + );
846 + return responderInstance;
847 }
848
879 -export function unmountEventComponent(
880 - eventComponentInstance: ReactDOMEventComponentInstance,
849 +export function unmountResponderInstance(
850 + responderInstance: ReactDOMEventResponderInstance,
851 ): void {
852 if (enableFlareAPI) {
853 // TODO stop listening to targetEventTypes
884 - unmountEventResponder(eventComponentInstance);
854 + unmountEventResponder(responderInstance);
855 }
856 }
857
packages/react-dom/src/events/DOMEventResponderSystem.js
+301 -317
@@ -13,14 +13,15 @@ import {
13 } from 'events/EventSystemFlags';
14 import type {AnyNativeEvent} from 'events/PluginModuleType';
15 import {
16 - EventComponent,
16 HostComponent,
17 FunctionComponent,
18 + MemoComponent,
19 + ForwardRef,
20 } from 'shared/ReactWorkTags';
21 import type {EventPriority} from 'shared/ReactTypes';
22 import type {
23 ReactDOMEventResponder,
23 - ReactDOMEventComponentInstance,
24 + ReactDOMEventResponderInstance,
25 ReactDOMResponderContext,
26 ReactDOMResponderEvent,
27 } from 'shared/ReactDOMTypes';
@@ -65,17 +66,11 @@ export function setListenToResponderEventTypes(
66 listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl;
67 }
68
68 -type EventObjectType = $Shape<PartialEventObject>;
69 -
70 -type EventQueue = {
71 - events: Array<EventObjectType>,
72 - eventPriority: EventPriority,
73 -};
74 -
75 -type PartialEventObject = {
76 - target: Element | Document,
77 - type: string,
78 -};
69 +type EventQueueItem = {|
70 + listeners: Array<(val: any) => void>,
71 + value: any,
72 +|};
73 +type EventQueue = Array<EventQueueItem>;
74
75 type ResponderTimeout = {|
76 id: TimeoutID,
@@ -83,114 +78,59 @@ type ResponderTimeout = {|
78 |};
79
80 type ResponderTimer = {|
86 - isHook: boolean,
87 - instance: ReactDOMEventComponentInstance,
81 + instance: ReactDOMEventResponderInstance,
82 func: () => void,
83 id: number,
84 timeStamp: number,
85 |};
86
87 const activeTimeouts: Map<number, ResponderTimeout> = new Map();
94 -const rootEventTypesToEventComponentInstances: Map<
88 +const rootEventTypesToEventResponderInstances: Map<
89 DOMTopLevelEventType | string,
96 - Set<ReactDOMEventComponentInstance>,
90 + Set<ReactDOMEventResponderInstance>,
91 > = new Map();
98 -const ownershipChangeListeners: Set<ReactDOMEventComponentInstance> = new Set();
99 -const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
100 -const eventListeners:
101 - | WeakMap
102 - | Map<
103 - $Shape<PartialEventObject>,
104 - ($Shape<PartialEventObject>) => void,
105 - > = new PossiblyWeakMap();
92 +const ownershipChangeListeners: Set<ReactDOMEventResponderInstance> = new Set();
93
94 let globalOwner = null;
108 -let continueLocalPropagation = false;
95
96 let currentTimeStamp = 0;
97 let currentTimers = new Map();
112 -let currentInstance: null | ReactDOMEventComponentInstance = null;
98 +let currentInstance: null | ReactDOMEventResponderInstance = null;
99 let currentEventQueue: null | EventQueue = null;
100 +let currentEventQueuePriority: EventPriority = ContinuousEvent;
101 let currentTimerIDCounter = 0;
102 let currentDocument: null | Document = null;
116 -let currentlyInHook = false;
103
104 const eventResponderContext: ReactDOMResponderContext = {
105 dispatchEvent(
120 - possibleEventObject: Object,
121 - listener: ($Shape<PartialEventObject>) => void,
106 + eventProp: string,
107 + eventValue: any,
108 eventPriority: EventPriority,
109 ): void {
110 validateResponderContext();
125 - const {target, type, timeStamp} = possibleEventObject;
126 -
127 - if (target == null || type == null || timeStamp == null) {
128 - throw new Error(
129 - 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
111 + validateEventValue(eventValue);
112 + if (eventPriority < currentEventQueuePriority) {
113 + currentEventQueuePriority = eventPriority;
114 + }
115 + const responderInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
116 + const target = responderInstance.fiber;
117 + const responder = responderInstance.responder;
118 + const listeners = collectListeners(eventProp, responder, target);
119 + if (listeners.length !== 0) {
120 + ((currentEventQueue: any): EventQueue).push(
121 + createEventQueueItem(eventValue, listeners),
122 );
123 }
132 - const showWarning = name => {
133 - if (__DEV__) {
134 - warning(
135 - false,
136 - '%s is not available on event objects created from event responder modules (React Flare). ' +
137 - 'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.%s }`',
138 - name,
139 - name,
140 - );
141 - }
142 - };
143 - possibleEventObject.preventDefault = () => {
144 - if (__DEV__) {
145 - showWarning('preventDefault()');
146 - }
147 - };
148 - possibleEventObject.stopPropagation = () => {
149 - if (__DEV__) {
150 - showWarning('stopPropagation()');
151 - }
152 - };
153 - possibleEventObject.isDefaultPrevented = () => {
154 - if (__DEV__) {
155 - showWarning('isDefaultPrevented()');
156 - }
157 - };
158 - possibleEventObject.isPropagationStopped = () => {
159 - if (__DEV__) {
160 - showWarning('isPropagationStopped()');
161 - }
162 - };
163 - // $FlowFixMe: we don't need value, Flow thinks we do
164 - Object.defineProperty(possibleEventObject, 'nativeEvent', {
165 - get() {
166 - if (__DEV__) {
167 - showWarning('nativeEvent');
168 - }
169 - },
170 - });
171 -
172 - const eventObject = ((possibleEventObject: any): $Shape<
173 - PartialEventObject,
174 - >);
175 - const eventQueue = ((currentEventQueue: any): EventQueue);
176 - eventQueue.eventPriority = eventPriority;
177 - eventListeners.set(eventObject, listener);
178 - eventQueue.events.push(eventObject);
124 },
180 - isTargetWithinEventComponent(target: Element | Document): boolean {
125 + isTargetWithinResponder(target: Element | Document): boolean {
126 validateResponderContext();
127 if (target != null) {
128 let fiber = getClosestInstanceFromNode(target);
184 - const currentFiber = ((currentInstance: any): ReactDOMEventComponentInstance)
185 - .currentFiber;
129 + const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
130 + .fiber;
131
132 while (fiber !== null) {
188 - const stateNode = fiber.stateNode;
189 - if (fiber.tag === EventComponent && stateNode !== null) {
190 - // Switch to the current fiber tree
191 - fiber = stateNode.currentFiber;
192 - }
193 - if (fiber === currentFiber || stateNode === currentInstance) {
133 + if (fiber === responderFiber || fiber.alternate === responderFiber) {
134 return true;
135 }
136 fiber = fiber.return;
@@ -198,28 +138,21 @@ const eventResponderContext: ReactDOMResponderContext = {
138 }
139 return false;
140 },
201 - isTargetWithinEventResponderScope(target: Element | Document): boolean {
141 + isTargetWithinResponderScope(target: Element | Document): boolean {
142 validateResponderContext();
203 - const componentInstance = ((currentInstance: any): ReactDOMEventComponentInstance);
143 + const componentInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
144 const responder = componentInstance.responder;
145
146 if (target != null) {
147 let fiber = getClosestInstanceFromNode(target);
208 - const currentFiber = ((currentInstance: any): ReactDOMEventComponentInstance)
209 - .currentFiber;
148 + const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
149 + .fiber;
150 +
151 while (fiber !== null) {
211 - const stateNode = fiber.stateNode;
212 - if (fiber.tag === EventComponent && stateNode !== null) {
213 - // Switch to the current fiber tree
214 - fiber = stateNode.currentFiber;
215 - }
216 - if (fiber === currentFiber || stateNode === currentInstance) {
152 + if (fiber === responderFiber || fiber.alternate === responderFiber) {
153 return true;
154 }
219 - if (
220 - fiber.tag === EventComponent &&
221 - (stateNode === null || stateNode.responder === responder)
222 - ) {
155 + if (doesFiberHaveResponder(fiber, responder)) {
156 return false;
157 }
158 fiber = fiber.return;
@@ -251,25 +184,25 @@ const eventResponderContext: ReactDOMResponderContext = {
184 listenToResponderEventTypesImpl(rootEventTypes, activeDocument);
185 for (let i = 0; i < rootEventTypes.length; i++) {
186 const rootEventType = rootEventTypes[i];
254 - const eventComponentInstance = ((currentInstance: any): ReactDOMEventComponentInstance);
255 - registerRootEventType(rootEventType, eventComponentInstance);
187 + const eventResponderInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
188 + registerRootEventType(rootEventType, eventResponderInstance);
189 }
190 },
191 removeRootEventTypes(rootEventTypes: Array<string>): void {
192 validateResponderContext();
193 for (let i = 0; i < rootEventTypes.length; i++) {
194 const rootEventType = rootEventTypes[i];
262 - let rootEventComponents = rootEventTypesToEventComponentInstances.get(
195 + let rootEventComponents = rootEventTypesToEventResponderInstances.get(
196 rootEventType,
197 );
265 - let rootEventTypesSet = ((currentInstance: any): ReactDOMEventComponentInstance)
198 + let rootEventTypesSet = ((currentInstance: any): ReactDOMEventResponderInstance)
199 .rootEventTypes;
200 if (rootEventTypesSet !== null) {
201 rootEventTypesSet.delete(rootEventType);
202 }
203 if (rootEventComponents !== undefined) {
204 rootEventComponents.delete(
272 - ((currentInstance: any): ReactDOMEventComponentInstance),
205 + ((currentInstance: any): ReactDOMEventResponderInstance),
206 );
207 }
208 }
@@ -289,8 +222,8 @@ const eventResponderContext: ReactDOMResponderContext = {
222 },
223 releaseOwnership(): boolean {
224 validateResponderContext();
292 - return releaseOwnershipForEventComponentInstance(
293 - ((currentInstance: any): ReactDOMEventComponentInstance),
225 + return releaseOwnershipForEventResponderInstance(
226 + ((currentInstance: any): ReactDOMEventResponderInstance),
227 );
228 },
229 setTimeout(func: () => void, delay): number {
@@ -313,8 +246,7 @@ const eventResponderContext: ReactDOMResponderContext = {
246 currentTimers.set(delay, timeout);
247 }
248 timeout.timers.set(timerId, {
316 - isHook: currentlyInHook,
317 - instance: ((currentInstance: any): ReactDOMEventComponentInstance),
249 + instance: ((currentInstance: any): ReactDOMEventResponderInstance),
250 func,
251 id: timerId,
252 timeStamp: currentTimeStamp,
@@ -334,11 +266,22 @@ const eventResponderContext: ReactDOMResponderContext = {
266 }
267 }
268 },
337 - getFocusableElementsInScope(): Array<HTMLElement> {
269 + getFocusableElementsInScope(deep: boolean): Array<HTMLElement> {
270 validateResponderContext();
271 const focusableElements = [];
340 - const eventComponentInstance = ((currentInstance: any): ReactDOMEventComponentInstance);
341 - const child = ((eventComponentInstance.currentFiber: any): Fiber).child;
272 + const eventResponderInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
273 + const currentResponder = eventResponderInstance.responder;
274 + let focusScopeFiber = eventResponderInstance.fiber;
275 + if (deep) {
276 + let deepNode = focusScopeFiber.return;
277 + while (deepNode !== null) {
278 + if (doesFiberHaveResponder(deepNode, currentResponder)) {
279 + focusScopeFiber = deepNode;
280 + }
281 + deepNode = deepNode.return;
282 + }
283 + }
284 + const child = focusScopeFiber.child;
285
286 if (child !== null) {
287 collectFocusableElements(child, focusableElements);
@@ -354,22 +297,11 @@ const eventResponderContext: ReactDOMResponderContext = {
297 isTargetWithinHostComponent(
298 target: Element | Document,
299 elementType: string,
357 - deep: boolean,
300 ): boolean {
301 validateResponderContext();
302 let fiber = getClosestInstanceFromNode(target);
361 - const currentResponder = ((currentInstance: any): ReactDOMEventComponentInstance)
362 - .responder;
303
304 while (fiber !== null) {
365 - const stateNode = fiber.stateNode;
366 - if (
367 - !deep &&
368 - (fiber.tag === EventComponent &&
369 - (stateNode === null || stateNode.responder === currentResponder))
370 - ) {
371 - return false;
372 - }
305 if (fiber.tag === HostComponent && fiber.type === elementType) {
306 return true;
307 }
@@ -377,16 +309,60 @@ const eventResponderContext: ReactDOMResponderContext = {
309 }
310 return false;
311 },
380 - continueLocalPropagation() {
381 - validateResponderContext();
382 - continueLocalPropagation = true;
383 - },
384 - isRespondingToHook() {
385 - return currentlyInHook;
386 - },
312 enqueueStateRestore,
313 };
314
315 +function validateEventValue(eventValue: any): void {
316 + if (typeof eventValue === 'object' && eventValue !== null) {
317 + const {target, type, timeStamp} = eventValue;
318 +
319 + if (target == null || type == null || timeStamp == null) {
320 + throw new Error(
321 + 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
322 + );
323 + }
324 + const showWarning = name => {
325 + if (__DEV__) {
326 + warning(
327 + false,
328 + '%s is not available on event objects created from event responder modules (React Flare). ' +
329 + 'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.%s }`',
330 + name,
331 + name,
332 + );
333 + }
334 + };
335 + eventValue.preventDefault = () => {
336 + if (__DEV__) {
337 + showWarning('preventDefault()');
338 + }
339 + };
340 + eventValue.stopPropagation = () => {
341 + if (__DEV__) {
342 + showWarning('stopPropagation()');
343 + }
344 + };
345 + eventValue.isDefaultPrevented = () => {
346 + if (__DEV__) {
347 + showWarning('isDefaultPrevented()');
348 + }
349 + };
350 + eventValue.isPropagationStopped = () => {
351 + if (__DEV__) {
352 + showWarning('isPropagationStopped()');
353 + }
354 + };
355 + // $FlowFixMe: we don't need value, Flow thinks we do
356 + Object.defineProperty(eventValue, 'nativeEvent', {
357 + get() {
358 + if (__DEV__) {
359 + showWarning('nativeEvent');
360 + }
361 + },
362 + });
363 + }
364 +}
365 +
366 function collectFocusableElements(
367 node: Fiber,
368 focusableElements: Array<HTMLElement>,
@@ -414,14 +390,40 @@ function collectFocusableElements(
390 }
391 }
392
393 +function createEventQueueItem(
394 + value: any,
395 + listeners: Array<(val: any) => void>,
396 +): EventQueueItem {
397 + return {
398 + value,
399 + listeners,
400 + };
401 +}
402 +
403 +function doesFiberHaveResponder(
404 + fiber: Fiber,
405 + responder: ReactDOMEventResponder,
406 +): boolean {
407 + if (fiber.tag === HostComponent) {
408 + const dependencies = fiber.dependencies;
409 + if (dependencies !== null) {
410 + const respondersMap = dependencies.responders;
411 + if (respondersMap !== null && respondersMap.has(responder)) {
412 + return true;
413 + }
414 + }
415 + }
416 + return false;
417 +}
418 +
419 function getActiveDocument(): Document {
420 return ((currentDocument: any): Document);
421 }
422
421 -function releaseOwnershipForEventComponentInstance(
422 - eventComponentInstance: ReactDOMEventComponentInstance,
423 +function releaseOwnershipForEventResponderInstance(
424 + eventResponderInstance: ReactDOMEventResponderInstance,
425 ): boolean {
424 - if (globalOwner === eventComponentInstance) {
426 + if (globalOwner === eventResponderInstance) {
427 globalOwner = null;
428 triggerOwnershipListeners();
429 return true;
@@ -461,13 +463,13 @@ function processTimers(
463 delay: number,
464 ): void {
465 const timersArr = Array.from(timers.values());
464 - currentEventQueue = createEventQueue();
466 + currentEventQueuePriority = ContinuousEvent;
467 try {
468 for (let i = 0; i < timersArr.length; i++) {
467 - const {isHook, instance, func, id, timeStamp} = timersArr[i];
469 + const {instance, func, id, timeStamp} = timersArr[i];
470 currentInstance = instance;
471 + currentEventQueue = [];
472 currentTimeStamp = timeStamp + delay;
470 - currentlyInHook = isHook;
473 try {
474 func();
475 } finally {
@@ -517,39 +519,83 @@ function createDOMResponderEvent(
519 };
520 }
521
520 -function createEventQueue(): EventQueue {
521 - return {
522 - events: [],
523 - eventPriority: ContinuousEvent,
524 - };
525 -}
526 -
527 -function processEvent(event: $Shape<PartialEventObject>): void {
528 - const type = event.type;
529 - const listener = ((eventListeners.get(event): any): (
530 - $Shape<PartialEventObject>,
531 - ) => void);
532 - invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
522 +function collectListeners(
523 + eventProp: string,
524 + eventResponder: ReactDOMEventResponder,
525 + target: Fiber,
526 +): Array<(any) => void> {
527 + const eventListeners = [];
528 + let node = target.return;
529 + nodeTraversal: while (node !== null) {
530 + switch (node.tag) {
531 + case HostComponent: {
532 + const dependencies = node.dependencies;
533 +
534 + if (dependencies !== null) {
535 + const respondersMap = dependencies.responders;
536 +
537 + if (respondersMap !== null && respondersMap.has(eventResponder)) {
538 + break nodeTraversal;
539 + }
540 + }
541 + break;
542 + }
543 + case FunctionComponent:
544 + case MemoComponent:
545 + case ForwardRef: {
546 + const dependencies = node.dependencies;
547 +
548 + if (dependencies !== null) {
549 + const listeners = dependencies.listeners;
550 +
551 + if (listeners !== null) {
552 + for (
553 + let s = 0, listenersLength = listeners.length;
554 + s < listenersLength;
555 + s++
556 + ) {
557 + const listener = listeners[s];
558 + const {responder, props} = listener;
559 + const listenerFunc = props[eventProp];
560 +
561 + if (
562 + responder === eventResponder &&
563 + typeof listenerFunc === 'function'
564 + ) {
565 + eventListeners.push(listenerFunc);
566 + }
567 + }
568 + }
569 + }
570 + }
571 + }
572 + node = node.return;
573 + }
574 + return eventListeners;
575 }
576
535 -function processEvents(events: Array<EventObjectType>): void {
536 - for (let i = 0, length = events.length; i < length; i++) {
537 - processEvent(events[i]);
577 +function processEvents(eventQueue: EventQueue): void {
578 + for (let i = 0, length = eventQueue.length; i < length; i++) {
579 + const {value, listeners} = eventQueue[i];
580 + for (let s = 0, length2 = listeners.length; s < length2; s++) {
581 + const listener = listeners[s];
582 + const type =
583 + typeof value === 'object' && value !== null ? value.type : '';
584 + invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, value);
585 + }
586 }
587 }
588
589 function processEventQueue(): void {
542 - const {events, eventPriority} = ((currentEventQueue: any): EventQueue);
543 -
544 - if (events.length === 0) {
590 + const eventQueue = ((currentEventQueue: any): EventQueue);
591 + if (eventQueue.length === 0) {
592 return;
593 }
547 -
548 - switch (eventPriority) {
594 + switch (currentEventQueuePriority) {
595 case DiscreteEvent: {
596 flushDiscreteUpdatesIfNeeded(currentTimeStamp);
597 discreteUpdates(() => {
552 - batchedEventUpdates(processEvents, events);
598 + batchedEventUpdates(processEvents, eventQueue);
599 });
600 break;
601 }
@@ -557,15 +603,15 @@ function processEventQueue(): void {
603 if (enableUserBlockingEvents) {
604 runWithPriority(
605 UserBlockingPriority,
560 - batchedEventUpdates.bind(null, processEvents, events),
606 + batchedEventUpdates.bind(null, processEvents, eventQueue),
607 );
608 } else {
563 - batchedEventUpdates(processEvents, events);
609 + batchedEventUpdates(processEvents, eventQueue);
610 }
611 break;
612 }
613 case ContinuousEvent: {
568 - batchedEventUpdates(processEvents, events);
614 + batchedEventUpdates(processEvents, eventQueue);
615 break;
616 }
617 }
@@ -583,36 +629,22 @@ function responderEventTypesContainType(
629 return false;
630 }
631
586 -function validateEventTargetTypesForResponder(
632 +function validateResponderTargetEventTypes(
633 eventType: string,
634 responder: ReactDOMEventResponder,
635 ): boolean {
590 - const targetEventTypes = responder.targetEventTypes;
636 + const {targetEventTypes} = responder;
637 // Validate the target event type exists on the responder
592 - if (targetEventTypes !== undefined) {
638 + if (targetEventTypes !== null) {
639 return responderEventTypesContainType(targetEventTypes, eventType);
640 }
641 return false;
642 }
643
598 -function handleTargetEventResponderInstance(
599 - responderEvent: ReactDOMResponderEvent,
600 - eventComponentInstance: ReactDOMEventComponentInstance,
601 - responder: ReactDOMEventResponder,
602 -): void {
603 - const {isHook, props, state} = eventComponentInstance;
604 - const onEvent = responder.onEvent;
605 - if (onEvent !== undefined) {
606 - currentInstance = eventComponentInstance;
607 - currentlyInHook = isHook;
608 - onEvent(responderEvent, eventResponderContext, props, state);
609 - }
610 -}
611 -
644 function validateOwnership(
613 - eventComponentInstance: ReactDOMEventComponentInstance,
645 + responderInstance: ReactDOMEventResponderInstance,
646 ): boolean {
615 - return globalOwner === null || globalOwner === eventComponentInstance;
647 + return globalOwner === null || globalOwner === responderInstance;
648 }
649
650 function traverseAndHandleEventResponderInstances(
@@ -628,8 +660,8 @@ function traverseAndHandleEventResponderInstances(
660 const eventType = isPassive ? topLevelType : topLevelType + '_active';
661
662 // Trigger event responders in this order:
631 - // - Bubble target phase
632 - // - Root phase
663 + // - Bubble target responder phase
664 + // - Root responder phase
665
666 const responderEvent = createDOMResponderEvent(
667 topLevelType,
@@ -638,75 +670,31 @@ function traverseAndHandleEventResponderInstances(
670 isPassiveEvent,
671 isPassiveSupported,
672 );
641 - const responderTargets = new Map();
642 - const allowLocalPropagation = new Set();
643 -
644 - // Bubbled event phases have the notion of local propagation.
645 - // This means that the propgation chain can be stopped part of the the way
646 - // through processing event component instances.
673 + const visitedResponders = new Set();
674 let node = targetFiber;
648 - let currentTarget = nativeEventTarget;
675 while (node !== null) {
650 - const {dependencies, stateNode, tag} = node;
651 - if (tag === HostComponent) {
652 - currentTarget = stateNode;
653 - } else if (tag === EventComponent) {
654 - const eventComponentInstance = stateNode;
655 - if (validateOwnership(eventComponentInstance)) {
656 - const responder = eventComponentInstance.responder;
657 - let responderTarget = responderTargets.get(responder);
658 - let skipCurrentNode = false;
659 -
660 - if (responderTarget === undefined) {
661 - if (validateEventTargetTypesForResponder(eventType, responder)) {
662 - responderTarget = currentTarget;
663 - responderTargets.set(responder, currentTarget);
664 - } else {
665 - skipCurrentNode = true;
666 - }
667 - } else if (allowLocalPropagation.has(responder)) {
668 - // TODO: remove continueLocalPropagation
669 - allowLocalPropagation.delete(responder);
670 - } else {
671 - skipCurrentNode = true;
672 - }
673 - if (!skipCurrentNode) {
674 - responderEvent.responderTarget = ((responderTarget: any):
675 - | Document
676 - | Element);
677 - // Switch to the current fiber tree
678 - node = eventComponentInstance.currentFiber;
679 - handleTargetEventResponderInstance(
680 - responderEvent,
681 - eventComponentInstance,
682 - responder,
683 - );
684 - // TODO: remove continueLocalPropagation
685 - if (continueLocalPropagation) {
686 - continueLocalPropagation = false;
687 - allowLocalPropagation.add(responder);
688 - }
689 - }
690 - }
691 - } else if (tag === FunctionComponent && dependencies !== null) {
692 - const events = dependencies.events;
693 - if (events !== null) {
694 - for (let i = 0; i < events.length; i++) {
695 - const eventComponentInstance = events[i];
696 - if (validateOwnership(eventComponentInstance)) {
697 - const responder = eventComponentInstance.responder;
698 - const responderTarget = responderTargets.get(responder);
699 - if (responderTarget !== undefined) {
700 - responderEvent.responderTarget = responderTarget;
701 - handleTargetEventResponderInstance(
702 - responderEvent,
703 - eventComponentInstance,
704 - responder,
705 - );
706 - // TODO: remove continueLocalPropagation
707 - if (continueLocalPropagation) {
708 - continueLocalPropagation = false;
709 - allowLocalPropagation.add(responder);
676 + const {dependencies, tag} = node;
677 + if (tag === HostComponent && dependencies !== null) {
678 + const respondersMap = dependencies.responders;
679 + if (respondersMap !== null) {
680 + const responderInstances = Array.from(respondersMap.values());
681 + for (let i = 0, length = responderInstances.length; i < length; i++) {
682 + const responderInstance = responderInstances[i];
683 +
684 + if (validateOwnership(responderInstance)) {
685 + const {props, responder, state, target} = responderInstance;
686 + if (
687 + !visitedResponders.has(responder) &&
688 + validateResponderTargetEventTypes(eventType, responder)
689 + ) {
690 + const onEvent = responder.onEvent;
691 + visitedResponders.add(responder);
692 + if (onEvent !== null) {
693 + currentInstance = responderInstance;
694 + responderEvent.responderTarget = ((target: any):
695 + | Element
696 + | Document);
697 + onEvent(responderEvent, eventResponderContext, props, state);
698 }
699 }
700 }
@@ -716,24 +704,22 @@ function traverseAndHandleEventResponderInstances(
704 node = node.return;
705 }
706 // Root phase
719 - const rootEventInstances = rootEventTypesToEventComponentInstances.get(
707 + const rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
708 eventType,
709 );
722 - if (rootEventInstances !== undefined) {
723 - const rootEventComponentInstances = Array.from(rootEventInstances);
710 + if (rootEventResponderInstances !== undefined) {
711 + const responderInstances = Array.from(rootEventResponderInstances);
712
725 - for (let i = 0; i < rootEventComponentInstances.length; i++) {
726 - const rootEventComponentInstance = rootEventComponentInstances[i];
727 - if (!validateOwnership(rootEventComponentInstance)) {
713 + for (let i = 0; i < responderInstances.length; i++) {
714 + const responderInstance = responderInstances[i];
715 + if (!validateOwnership(responderInstance)) {
716 continue;
717 }
730 - const {isHook, props, responder, state} = rootEventComponentInstance;
718 + const {props, responder, state, target} = responderInstance;
719 const onRootEvent = responder.onRootEvent;
732 - if (onRootEvent !== undefined) {
733 - currentInstance = rootEventComponentInstance;
734 - currentlyInHook = isHook;
735 - const responderTarget = responderTargets.get(responder);
736 - responderEvent.responderTarget = responderTarget || null;
720 + if (onRootEvent !== null) {
721 + currentInstance = responderInstance;
722 + responderEvent.responderTarget = ((target: any): Element | Document);
723 onRootEvent(responderEvent, eventResponderContext, props, state);
724 }
725 }
@@ -743,40 +729,43 @@ function traverseAndHandleEventResponderInstances(
729 function triggerOwnershipListeners(): void {
730 const listeningInstances = Array.from(ownershipChangeListeners);
731 const previousInstance = currentInstance;
746 - const previouslyInHook = currentlyInHook;
747 - currentEventQueue = createEventQueue();
732 + const previousEventQueuePriority = currentEventQueuePriority;
733 + const previousEventQueue = currentEventQueue;
734 try {
735 for (let i = 0; i < listeningInstances.length; i++) {
736 const instance = listeningInstances[i];
751 - const {isHook, props, responder, state} = instance;
737 + const {props, responder, state} = instance;
738 currentInstance = instance;
753 - currentlyInHook = isHook;
739 + currentEventQueuePriority = ContinuousEvent;
740 + currentEventQueue = [];
741 const onOwnershipChange = ((responder: any): ReactDOMEventResponder)
742 .onOwnershipChange;
756 - if (onOwnershipChange !== undefined) {
743 + if (onOwnershipChange !== null) {
744 onOwnershipChange(eventResponderContext, props, state);
745 }
746 }
747 processEventQueue();
748 } finally {
749 currentInstance = previousInstance;
763 - currentlyInHook = previouslyInHook;
750 + currentEventQueue = previousEventQueue;
751 + currentEventQueuePriority = previousEventQueuePriority;
752 }
753 }
754
755 export function mountEventResponder(
768 - eventComponentInstance: ReactDOMEventComponentInstance,
756 + responder: ReactDOMEventResponder,
757 + responderInstance: ReactDOMEventResponderInstance,
758 + props: Object,
759 + state: Object,
760 ) {
770 - const responder = ((eventComponentInstance.responder: any): ReactDOMEventResponder);
771 - if (responder.onOwnershipChange !== undefined) {
772 - ownershipChangeListeners.add(eventComponentInstance);
761 + if (responder.onOwnershipChange !== null) {
762 + ownershipChangeListeners.add(responderInstance);
763 }
764 const onMount = responder.onMount;
775 - if (onMount !== undefined) {
776 - let {isHook, props, state} = eventComponentInstance;
777 - currentEventQueue = createEventQueue();
778 - currentInstance = eventComponentInstance;
779 - currentlyInHook = isHook;
765 + if (onMount !== null) {
766 + currentEventQueuePriority = ContinuousEvent;
767 + currentInstance = responderInstance;
768 + currentEventQueue = [];
769 try {
770 onMount(eventResponderContext, props, state);
771 processEventQueue();
@@ -789,15 +778,15 @@ export function mountEventResponder(
778 }
779
780 export function unmountEventResponder(
792 - eventComponentInstance: ReactDOMEventComponentInstance,
781 + responderInstance: ReactDOMEventResponderInstance,
782 ): void {
794 - const responder = ((eventComponentInstance.responder: any): ReactDOMEventResponder);
783 + const responder = ((responderInstance.responder: any): ReactDOMEventResponder);
784 const onUnmount = responder.onUnmount;
796 - if (onUnmount !== undefined) {
797 - let {isHook, props, state} = eventComponentInstance;
798 - currentEventQueue = createEventQueue();
799 - currentInstance = eventComponentInstance;
800 - currentlyInHook = isHook;
785 + if (onUnmount !== null) {
786 + let {props, state} = responderInstance;
787 + currentEventQueue = [];
788 + currentEventQueuePriority = ContinuousEvent;
789 + currentInstance = responderInstance;
790 try {
791 onUnmount(eventResponderContext, props, state);
792 processEventQueue();
@@ -807,27 +796,21 @@ export function unmountEventResponder(
796 currentTimers = null;
797 }
798 }
810 - try {
811 - currentEventQueue = createEventQueue();
812 - releaseOwnershipForEventComponentInstance(eventComponentInstance);
813 - processEventQueue();
814 - } finally {
815 - currentEventQueue = null;
816 - }
817 - if (responder.onOwnershipChange !== undefined) {
818 - ownershipChangeListeners.delete(eventComponentInstance);
799 + releaseOwnershipForEventResponderInstance(responderInstance);
800 + if (responder.onOwnershipChange !== null) {
801 + ownershipChangeListeners.delete(responderInstance);
802 }
820 - const rootEventTypesSet = eventComponentInstance.rootEventTypes;
803 + const rootEventTypesSet = responderInstance.rootEventTypes;
804 if (rootEventTypesSet !== null) {
805 const rootEventTypes = Array.from(rootEventTypesSet);
806
807 for (let i = 0; i < rootEventTypes.length; i++) {
808 const topLevelEventType = rootEventTypes[i];
826 - let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
809 + let rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
810 topLevelEventType,
811 );
829 - if (rootEventComponentInstances !== undefined) {
830 - rootEventComponentInstances.delete(eventComponentInstance);
812 + if (rootEventResponderInstances !== undefined) {
813 + rootEventResponderInstances.delete(responderInstance);
814 }
815 }
816 }
@@ -835,7 +818,7 @@ export function unmountEventResponder(
818
819 function validateResponderContext(): void {
820 invariant(
838 - currentEventQueue && currentInstance,
821 + currentInstance !== null,
822 'An event responder context was used outside of an event cycle. ' +
823 'Use context.setTimeout() to use asynchronous responder context outside of event cycle .',
824 );
@@ -854,9 +837,10 @@ export function dispatchEventForResponderEventSystem(
837 const previousTimers = currentTimers;
838 const previousTimeStamp = currentTimeStamp;
839 const previousDocument = currentDocument;
857 - const previouslyInHook = currentlyInHook;
840 + const previousEventQueuePriority = currentEventQueuePriority;
841 currentTimers = null;
859 - currentEventQueue = createEventQueue();
842 + currentEventQueue = [];
843 + currentEventQueuePriority = ContinuousEvent;
844 // nodeType 9 is DOCUMENT_NODE
845 currentDocument =
846 (nativeEventTarget: any).nodeType === 9
@@ -879,38 +863,38 @@ export function dispatchEventForResponderEventSystem(
863 currentEventQueue = previousEventQueue;
864 currentTimeStamp = previousTimeStamp;
865 currentDocument = previousDocument;
882 - currentlyInHook = previouslyInHook;
866 + currentEventQueuePriority = previousEventQueuePriority;
867 }
868 }
869 }
870
887 -export function addRootEventTypesForComponentInstance(
888 - eventComponentInstance: ReactDOMEventComponentInstance,
871 +export function addRootEventTypesForResponderInstance(
872 + responderInstance: ReactDOMEventResponderInstance,
873 rootEventTypes: Array<string>,
874 ): void {
875 for (let i = 0; i < rootEventTypes.length; i++) {
876 const rootEventType = rootEventTypes[i];
893 - registerRootEventType(rootEventType, eventComponentInstance);
877 + registerRootEventType(rootEventType, responderInstance);
878 }
879 }
880
881 function registerRootEventType(
882 rootEventType: string,
899 - eventComponentInstance: ReactDOMEventComponentInstance,
883 + eventResponderInstance: ReactDOMEventResponderInstance,
884 ): void {
901 - let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
885 + let rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
886 rootEventType,
887 );
904 - if (rootEventComponentInstances === undefined) {
905 - rootEventComponentInstances = new Set();
906 - rootEventTypesToEventComponentInstances.set(
888 + if (rootEventResponderInstances === undefined) {
889 + rootEventResponderInstances = new Set();
890 + rootEventTypesToEventResponderInstances.set(
891 rootEventType,
908 - rootEventComponentInstances,
892 + rootEventResponderInstances,
893 );
894 }
911 - let rootEventTypesSet = eventComponentInstance.rootEventTypes;
895 + let rootEventTypesSet = eventResponderInstance.rootEventTypes;
896 if (rootEventTypesSet === null) {
913 - rootEventTypesSet = eventComponentInstance.rootEventTypes = new Set();
897 + rootEventTypesSet = eventResponderInstance.rootEventTypes = new Set();
898 }
899 invariant(
900 !rootEventTypesSet.has(rootEventType),
@@ -920,5 +904,5 @@ function registerRootEventType(
904 rootEventType,
905 );
906 rootEventTypesSet.add(rootEventType);
923 - rootEventComponentInstances.add(eventComponentInstance);
907 + rootEventResponderInstances.add(eventResponderInstance);
908 }
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+203 -259
@@ -17,29 +17,26 @@ let ReactDOM;
17 // now it's an enum but is that what we want? Hard coding this for now.
18 const DiscreteEvent = 0;
19
20 -function createReactEventComponent({
21 - targetEventTypes,
22 - rootEventTypes,
23 - getInitialState,
20 +function createEventResponder({
21 onEvent,
22 onRootEvent,
23 + rootEventTypes,
24 + targetEventTypes,
25 onMount,
26 onUnmount,
27 onOwnershipChange,
28 + getInitialState,
29 }) {
30 - const testEventResponder = {
31 - displayName: 'TestEventComponent',
30 + return React.unstable_createResponder('TestEventComponent', {
31 targetEventTypes,
32 rootEventTypes,
34 - getInitialState,
33 onEvent,
34 onRootEvent,
35 onMount,
36 onUnmount,
37 onOwnershipChange,
40 - };
41 -
42 - return React.unstable_createEvent(testEventResponder);
38 + getInitialState,
39 + });
40 }
41
42 const createEvent = (type, data) => {
@@ -83,12 +80,12 @@ describe('DOMEventResponderSystem', () => {
80 container = null;
81 });
82
86 - it('the event responder event listeners should fire on click event', () => {
83 + it('the event responders should fire on click event', () => {
84 let eventResponderFiredCount = 0;
85 let eventLog = [];
86 const buttonRef = React.createRef();
87
91 - const ClickEventComponent = createReactEventComponent({
88 + const TestResponder = createEventResponder({
89 targetEventTypes: ['click'],
90 onEvent: (event, context, props) => {
91 eventResponderFiredCount++;
@@ -102,9 +99,9 @@ describe('DOMEventResponderSystem', () => {
99 });
100
101 const Test = () => (
105 - <ClickEventComponent>
106 - <button ref={buttonRef}>Click me!</button>
107 - </ClickEventComponent>
102 + <button ref={buttonRef} responders={<TestResponder />}>
103 + Click me!
104 + </button>
105 );
106
107 ReactDOM.render(<Test />, container);
@@ -137,7 +134,7 @@ describe('DOMEventResponderSystem', () => {
134 expect(eventResponderFiredCount).toBe(2);
135 });
136
140 - it('the event responder event listeners should fire on click event (passive events forced)', () => {
137 + it('the event responders should fire on click event (passive events forced)', () => {
138 // JSDOM does not support passive events, so this manually overrides the value to be true
139 const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
140 checkPassiveEvents.passiveBrowserEventsSupported = true;
@@ -145,7 +142,7 @@ describe('DOMEventResponderSystem', () => {
142 let eventLog = [];
143 const buttonRef = React.createRef();
144
148 - const ClickEventComponent = createReactEventComponent({
145 + const TestResponder = createEventResponder({
146 targetEventTypes: ['click'],
147 onEvent: (event, context, props) => {
148 eventLog.push({
@@ -158,9 +155,9 @@ describe('DOMEventResponderSystem', () => {
155 });
156
157 const Test = () => (
161 - <ClickEventComponent>
162 - <button ref={buttonRef}>Click me!</button>
163 - </ClickEventComponent>
158 + <button ref={buttonRef} responders={<TestResponder />}>
159 + Click me!
160 + </button>
161 );
162
163 ReactDOM.render(<Test />, container);
@@ -179,15 +176,14 @@ describe('DOMEventResponderSystem', () => {
176 ]);
177 });
178
182 - it('nested event responders and their event listeners should fire multiple times', () => {
179 + it('nested event responders should not fire multiple times', () => {
180 let eventResponderFiredCount = 0;
181 let eventLog = [];
182 const buttonRef = React.createRef();
183
187 - const ClickEventComponent = createReactEventComponent({
184 + const TestResponder = createEventResponder({
185 targetEventTypes: ['click'],
186 onEvent: (event, context, props) => {
190 - context.continueLocalPropagation();
187 eventResponderFiredCount++;
188 eventLog.push({
189 name: event.type,
@@ -198,12 +194,12 @@ describe('DOMEventResponderSystem', () => {
194 },
195 });
196
201 - const Test = () => (
202 - <ClickEventComponent>
203 - <ClickEventComponent>
204 - <button ref={buttonRef}>Click me!</button>
205 - </ClickEventComponent>
206 - </ClickEventComponent>
197 + let Test = () => (
198 + <button
199 + ref={buttonRef}
200 + responders={[<TestResponder />, <TestResponder />]}>
201 + Click me!
202 + </button>
203 );
204
205 ReactDOM.render(<Test />, container);
@@ -211,8 +207,8 @@ describe('DOMEventResponderSystem', () => {
207 // Clicking the button should trigger the event responder onEvent()
208 let buttonElement = buttonRef.current;
209 dispatchClickEvent(buttonElement);
214 - expect(eventResponderFiredCount).toBe(2);
215 - expect(eventLog.length).toBe(2);
210 + expect(eventResponderFiredCount).toBe(1);
211 + expect(eventLog.length).toBe(1);
212 // JSDOM does not support passive events, so this will be false
213 expect(eventLog).toEqual([
214 {
@@ -221,6 +217,27 @@ describe('DOMEventResponderSystem', () => {
217 passiveSupported: false,
218 phase: 'bubble',
219 },
220 + ]);
221 +
222 + eventLog = [];
223 +
224 + Test = () => (
225 + <div responders={<TestResponder />}>
226 + <button ref={buttonRef} responders={<TestResponder />}>
227 + Click me!
228 + </button>
229 + </div>
230 + );
231 +
232 + ReactDOM.render(<Test />, container);
233 +
234 + // Clicking the button should trigger the event responder onEvent()
235 + buttonElement = buttonRef.current;
236 + dispatchClickEvent(buttonElement);
237 + expect(eventResponderFiredCount).toBe(2);
238 + expect(eventLog.length).toBe(1);
239 +
240 + expect(eventLog).toEqual([
241 {
242 name: 'click',
243 passive: false,
@@ -230,30 +247,30 @@ describe('DOMEventResponderSystem', () => {
247 ]);
248 });
249
233 - it('nested event responders and their event listeners should fire in the correct order', () => {
250 + it('nested event responders should fire in the correct order', () => {
251 let eventLog = [];
252 const buttonRef = React.createRef();
253
237 - const ClickEventComponentA = createReactEventComponent({
254 + const TestResponderA = createEventResponder({
255 targetEventTypes: ['click'],
256 onEvent: (event, context, props) => {
257 eventLog.push(`A [bubble]`);
258 },
259 });
260
244 - const ClickEventComponentB = createReactEventComponent({
261 + const TestResponderB = createEventResponder({
262 targetEventTypes: ['click'],
263 onEvent: (event, context, props) => {
264 eventLog.push(`B [bubble]`);
265 },
266 });
267
251 - const Test = () => (
252 - <ClickEventComponentA>
253 - <ClickEventComponentB>
254 - <button ref={buttonRef}>Click me!</button>
255 - </ClickEventComponentB>
256 - </ClickEventComponentA>
268 + let Test = () => (
269 + <button
270 + ref={buttonRef}
271 + responders={[<TestResponderA />, <TestResponderB />]}>
272 + Click me!
273 + </button>
274 );
275
276 ReactDOM.render(<Test />, container);
@@ -262,33 +279,22 @@ describe('DOMEventResponderSystem', () => {
279 let buttonElement = buttonRef.current;
280 dispatchClickEvent(buttonElement);
281
265 - expect(eventLog).toEqual(['B [bubble]', 'A [bubble]']);
266 - });
282 + expect(eventLog).toEqual(['A [bubble]', 'B [bubble]']);
283
268 - it('nested event responders should fire in the correct order with continueLocalPropagation', () => {
269 - let eventLog = [];
270 - const buttonRef = React.createRef();
271 -
272 - const ClickEventComponent = createReactEventComponent({
273 - targetEventTypes: ['click'],
274 - onEvent: (event, context, props) => {
275 - context.continueLocalPropagation();
276 - eventLog.push(`${props.name} [bubble]`);
277 - },
278 - });
284 + eventLog = [];
285
280 - const Test = () => (
281 - <ClickEventComponent name="A">
282 - <ClickEventComponent name="B">
283 - <button ref={buttonRef}>Click me!</button>
284 - </ClickEventComponent>
285 - </ClickEventComponent>
286 + Test = () => (
287 + <div responders={<TestResponderA />}>
288 + <button ref={buttonRef} responders={<TestResponderB />}>
289 + Click me!
290 + </button>
291 + </div>
292 );
293
294 ReactDOM.render(<Test />, container);
295
296 // Clicking the button should trigger the event responder onEvent()
291 - let buttonElement = buttonRef.current;
297 + buttonElement = buttonRef.current;
298 dispatchClickEvent(buttonElement);
299
300 expect(eventLog).toEqual(['B [bubble]', 'A [bubble]']);
@@ -298,7 +304,7 @@ describe('DOMEventResponderSystem', () => {
304 let eventLog = [];
305 const buttonRef = React.createRef();
306
301 - const ClickEventComponent = createReactEventComponent({
307 + const TestResponder = createEventResponder({
308 targetEventTypes: ['click'],
309 onEvent: (event, context, props) => {
310 eventLog.push(`${props.name} [bubble]`);
@@ -306,11 +312,11 @@ describe('DOMEventResponderSystem', () => {
312 });
313
314 const Test = () => (
309 - <ClickEventComponent name="A">
310 - <ClickEventComponent name="B">
311 - <button ref={buttonRef}>Click me!</button>
312 - </ClickEventComponent>
313 - </ClickEventComponent>
315 + <div responders={<TestResponder name="A" />}>
316 + <button ref={buttonRef} responders={<TestResponder name="B" />}>
317 + Click me!
318 + </button>
319 + </div>
320 );
321
322 ReactDOM.render(<Test />, container);
@@ -326,22 +332,16 @@ describe('DOMEventResponderSystem', () => {
332 let eventLog = [];
333 const buttonRef = React.createRef();
334
329 - const ClickEventComponent = createReactEventComponent({
335 + const TestResponder = createEventResponder({
336 targetEventTypes: ['click'],
337 onEvent: (event, context, props) => {
332 - if (props.onMagicClick) {
333 - const syntheticEvent = {
334 - target: event.target,
335 - type: 'magicclick',
336 - phase: 'bubble',
337 - timeStamp: context.getTimeStamp(),
338 - };
339 - context.dispatchEvent(
340 - syntheticEvent,
341 - props.onMagicClick,
342 - DiscreteEvent,
343 - );
344 - }
338 + const syntheticEvent = {
339 + target: event.target,
340 + type: 'magicclick',
341 + phase: 'bubble',
342 + timeStamp: context.getTimeStamp(),
343 + };
344 + context.dispatchEvent('onMagicClick', syntheticEvent, DiscreteEvent);
345 },
346 });
347
@@ -349,11 +349,17 @@ describe('DOMEventResponderSystem', () => {
349 eventLog.push('magic event fired', e.type, e.phase);
350 }
351
352 - const Test = () => (
353 - <ClickEventComponent onMagicClick={handleMagicEvent}>
354 - <button ref={buttonRef}>Click me!</button>
355 - </ClickEventComponent>
356 - );
352 + const Test = () => {
353 + React.unstable_useListener(TestResponder, {
354 + onMagicClick: handleMagicEvent,
355 + });
356 +
357 + return (
358 + <button ref={buttonRef} responders={<TestResponder />}>
359 + Click me!
360 + </button>
361 + );
362 + };
363
364 ReactDOM.render(<Test />, container);
365
@@ -375,40 +381,32 @@ describe('DOMEventResponderSystem', () => {
381 phase,
382 timeStamp: context.getTimeStamp(),
383 };
378 - context.dispatchEvent(pressEvent, props.onPress, DiscreteEvent);
384 + context.dispatchEvent('onPress', pressEvent, DiscreteEvent);
385
386 context.setTimeout(() => {
381 - if (props.onLongPress) {
382 - const longPressEvent = {
383 - target: event.target,
384 - type: 'longpress',
385 - phase,
386 - timeStamp: context.getTimeStamp(),
387 - };
388 - context.dispatchEvent(
389 - longPressEvent,
390 - props.onLongPress,
391 - DiscreteEvent,
392 - );
393 - }
394 -
395 - if (props.onLongPressChange) {
396 - const longPressChangeEvent = {
397 - target: event.target,
398 - type: 'longpresschange',
399 - phase,
400 - timeStamp: context.getTimeStamp(),
401 - };
402 - context.dispatchEvent(
403 - longPressChangeEvent,
404 - props.onLongPressChange,
405 - DiscreteEvent,
406 - );
407 - }
387 + const longPressEvent = {
388 + target: event.target,
389 + type: 'longpress',
390 + phase,
391 + timeStamp: context.getTimeStamp(),
392 + };
393 + context.dispatchEvent('onLongPress', longPressEvent, DiscreteEvent);
394 +
395 + const longPressChangeEvent = {
396 + target: event.target,
397 + type: 'longpresschange',
398 + phase,
399 + timeStamp: context.getTimeStamp(),
400 + };
401 + context.dispatchEvent(
402 + 'onLongPressChange',
403 + longPressChangeEvent,
404 + DiscreteEvent,
405 + );
406 }, 500);
407 }
408
411 - const LongPressEventComponent = createReactEventComponent({
409 + const TestResponder = createEventResponder({
410 targetEventTypes: ['click'],
411 onEvent: (event, context, props) => {
412 handleEvent(event, context, props, 'bubble');
@@ -419,14 +417,19 @@ describe('DOMEventResponderSystem', () => {
417 eventLog.push(msg);
418 }
419
422 - const Test = () => (
423 - <LongPressEventComponent
424 - onPress={e => log('press ' + e.phase)}
425 - onLongPress={e => log('longpress ' + e.phase)}
426 - onLongPressChange={e => log('longpresschange ' + e.phase)}>
427 - <button ref={buttonRef}>Click me!</button>
428 - </LongPressEventComponent>
429 - );
420 + const Test = () => {
421 + React.unstable_useListener(TestResponder, {
422 + onPress: e => log('press ' + e.phase),
423 + onLongPress: e => log('longpress ' + e.phase),
424 + onLongPressChange: e => log('longpresschange ' + e.phase),
425 + });
426 +
427 + return (
428 + <button ref={buttonRef} responders={<TestResponder />}>
429 + Click me!
430 + </button>
431 + );
432 + };
433
434 ReactDOM.render(<Test />, container);
435
@@ -445,48 +448,68 @@ describe('DOMEventResponderSystem', () => {
448 it('the event responder onMount() function should fire', () => {
449 let onMountFired = 0;
450
448 - const EventComponent = createReactEventComponent({
451 + const TestResponder = createEventResponder({
452 targetEventTypes: [],
453 onMount: () => {
454 onMountFired++;
455 },
456 });
457
455 - const Test = () => (
456 - <EventComponent>
457 - <button />
458 - </EventComponent>
458 + const TestResponder2 = createEventResponder({
459 + targetEventTypes: [],
460 + onMount: () => {
461 + onMountFired++;
462 + },
463 + });
464 +
465 + ReactDOM.render(
466 + <button responders={[<TestResponder />, <TestResponder2 />]} />,
467 + container,
468 );
469 + expect(onMountFired).toEqual(2);
470
461 - ReactDOM.render(<Test />, container);
462 - expect(onMountFired).toEqual(1);
471 + ReactDOM.render(
472 + <button responders={[<TestResponder2 />, <TestResponder />]} />,
473 + container,
474 + );
475 + expect(onMountFired).toEqual(2);
476 });
477
478 it('the event responder onUnmount() function should fire', () => {
479 let onUnmountFired = 0;
480
468 - const EventComponent = createReactEventComponent({
481 + const TestResponder = createEventResponder({
482 targetEventTypes: [],
483 onUnmount: () => {
484 onUnmountFired++;
485 },
486 });
487
475 - const Test = () => (
476 - <EventComponent>
477 - <button />
478 - </EventComponent>
479 - );
480 -
481 - ReactDOM.render(<Test />, container);
488 + ReactDOM.render(<button responders={[<TestResponder />]} />, container);
489 ReactDOM.render(null, container);
490 expect(onUnmountFired).toEqual(1);
491 +
492 + ReactDOM.render(<button responders={[<TestResponder />]} />, container);
493 + ReactDOM.render(<button responders={null} />, container);
494 + expect(onUnmountFired).toEqual(2);
495 +
496 + ReactDOM.render(<button responders={[<TestResponder />]} />, container);
497 + ReactDOM.render(<button responders={[]} />, container);
498 + expect(onUnmountFired).toEqual(3);
499 +
500 + ReactDOM.render(<button responders={[<TestResponder />]} />, container);
501 + ReactDOM.render(<button />, container);
502 + expect(onUnmountFired).toEqual(4);
503 +
504 + ReactDOM.render(<button responders={[<TestResponder />]} />, container);
505 + ReactDOM.render(<button responders={<TestResponder />} />, container);
506 + expect(onUnmountFired).toEqual(4);
507 });
508
509 it('the event responder onUnmount() function should fire with state', () => {
510 let counter = 0;
511
489 - const EventComponent = createReactEventComponent({
512 + const TestResponder = createEventResponder({
513 targetEventTypes: [],
514 getInitialState: () => ({
515 incrementAmount: 5,
@@ -496,11 +519,7 @@ describe('DOMEventResponderSystem', () => {
519 },
520 });
521
499 - const Test = () => (
500 - <EventComponent>
501 - <button />
502 - </EventComponent>
503 - );
522 + const Test = () => <button responders={<TestResponder />} />;
523
524 ReactDOM.render(<Test />, container);
525 ReactDOM.render(null, container);
@@ -512,7 +531,7 @@ describe('DOMEventResponderSystem', () => {
531 let ownershipGained = false;
532 const buttonRef = React.createRef();
533
515 - const EventComponent = createReactEventComponent({
534 + const TestResponder = createEventResponder({
535 targetEventTypes: ['click'],
536 onEvent: (event, context, props, state) => {
537 ownershipGained = context.requestGlobalOwnership();
@@ -523,9 +542,7 @@ describe('DOMEventResponderSystem', () => {
542 });
543
544 const Test = () => (
526 - <EventComponent>
527 - <button ref={buttonRef} />
528 - </EventComponent>
545 + <button ref={buttonRef} responders={<TestResponder />} />
546 );
547
548 ReactDOM.render(<Test />, container);
@@ -543,7 +560,7 @@ describe('DOMEventResponderSystem', () => {
560 let eventResponderFiredCount = 0;
561 let eventLog = [];
562
546 - const ClickEventComponent = createReactEventComponent({
563 + const TestResponder = createEventResponder({
564 rootEventTypes: ['click'],
565 onRootEvent: event => {
566 eventResponderFiredCount++;
@@ -557,9 +574,7 @@ describe('DOMEventResponderSystem', () => {
574 });
575
576 const Test = () => (
560 - <ClickEventComponent>
561 - <button>Click me!</button>
562 - </ClickEventComponent>
577 + <button responders={<TestResponder />}>Click me!</button>
578 );
579
580 ReactDOM.render(<Test />, container);
@@ -579,50 +594,13 @@ describe('DOMEventResponderSystem', () => {
594 ]);
595 });
596
582 - it('isTargetWithinEventResponderScope works', () => {
583 - const buttonRef = React.createRef();
584 - const divRef = React.createRef();
585 - const log = [];
586 -
587 - const EventComponent = createReactEventComponent({
588 - targetEventTypes: ['pointerout'],
589 - onEvent: (event, context) => {
590 - context.continueLocalPropagation();
591 - const isWithin = context.isTargetWithinEventResponderScope(
592 - event.nativeEvent.relatedTarget,
593 - );
594 - log.push(isWithin);
595 - },
596 - allowMultipleHostChildren: true,
597 - });
598 -
599 - const Test = () => (
600 - <EventComponent>
601 - <div ref={divRef} />
602 - <EventComponent>
603 - <button ref={buttonRef}>Click me!</button>
604 - </EventComponent>
605 - </EventComponent>
606 - );
607 - ReactDOM.render(<Test />, container);
608 -
609 - buttonRef.current.dispatchEvent(
610 - createEvent('pointerout', {relatedTarget: divRef.current}),
611 - );
612 - divRef.current.dispatchEvent(
613 - createEvent('pointerout', {relatedTarget: buttonRef.current}),
614 - );
615 -
616 - expect(log).toEqual([false, true, false]);
617 - });
618 -
597 it('the event responder target listeners should correctly fire for only their events', () => {
598 let clickEventComponent1Fired = 0;
599 let clickEventComponent2Fired = 0;
600 let eventLog = [];
601 const buttonRef = React.createRef();
602
625 - const ClickEventComponent1 = createReactEventComponent({
603 + const TestResponderA = createEventResponder({
604 targetEventTypes: ['click_active'],
605 onEvent: event => {
606 clickEventComponent1Fired++;
@@ -634,7 +612,7 @@ describe('DOMEventResponderSystem', () => {
612 },
613 });
614
637 - const ClickEventComponent2 = createReactEventComponent({
615 + const TestResponderB = createEventResponder({
616 targetEventTypes: ['click'],
617 onEvent: event => {
618 clickEventComponent2Fired++;
@@ -647,11 +625,11 @@ describe('DOMEventResponderSystem', () => {
625 });
626
627 const Test = () => (
650 - <ClickEventComponent1>
651 - <ClickEventComponent2>
652 - <button ref={buttonRef}>Click me!</button>
653 - </ClickEventComponent2>
654 - </ClickEventComponent1>
628 + <div responders={<TestResponderA />}>
629 + <button ref={buttonRef} responders={<TestResponderB />}>
630 + Click me!
631 + </button>
632 + </div>
633 );
634
635 ReactDOM.render(<Test />, container);
@@ -681,7 +659,7 @@ describe('DOMEventResponderSystem', () => {
659 let clickEventComponent2Fired = 0;
660 let eventLog = [];
661
684 - const ClickEventComponent1 = createReactEventComponent({
662 + const TestResponderA = createEventResponder({
663 rootEventTypes: ['click_active'],
664 onRootEvent: event => {
665 clickEventComponent1Fired++;
@@ -693,7 +671,7 @@ describe('DOMEventResponderSystem', () => {
671 },
672 });
673
696 - const ClickEventComponent2 = createReactEventComponent({
674 + const TestResponderB = createEventResponder({
675 rootEventTypes: ['click'],
676 onRootEvent: event => {
677 clickEventComponent2Fired++;
@@ -706,11 +684,9 @@ describe('DOMEventResponderSystem', () => {
684 });
685
686 const Test = () => (
709 - <ClickEventComponent1>
710 - <ClickEventComponent2>
711 - <button>Click me!</button>
712 - </ClickEventComponent2>
713 - </ClickEventComponent1>
687 + <div responders={<TestResponderA />}>
688 + <button responders={<TestResponderB />}>Click me!</button>
689 + </div>
690 );
691
692 ReactDOM.render(<Test />, container);
@@ -737,7 +713,7 @@ describe('DOMEventResponderSystem', () => {
713 });
714
715 it('the event responder system should warn on accessing invalid properties', () => {
740 - const ClickEventComponent = createReactEventComponent({
716 + const TestResponder = createEventResponder({
717 rootEventTypes: ['click'],
718 onRootEvent: (event, context, props) => {
719 const syntheticEvent = {
@@ -745,16 +721,18 @@ describe('DOMEventResponderSystem', () => {
721 type: 'click',
722 timeStamp: context.getTimeStamp(),
723 };
748 - context.dispatchEvent(syntheticEvent, props.onClick, DiscreteEvent);
724 + context.dispatchEvent('onClick', syntheticEvent, DiscreteEvent);
725 },
726 });
727
728 let handler;
753 - const Test = () => (
754 - <ClickEventComponent onClick={handler}>
755 - <button>Click me!</button>
756 - </ClickEventComponent>
757 - );
729 + const Test = () => {
730 + React.unstable_useListener(TestResponder, {
731 + onClick: handler,
732 + });
733 +
734 + return <button responders={<TestResponder />}>Click me!</button>;
735 + };
736 expect(() => {
737 handler = event => {
738 event.preventDefault();
@@ -818,79 +796,45 @@ describe('DOMEventResponderSystem', () => {
796 expect(container.innerHTML).toBe('<button>Click me!</button>');
797 });
798
821 - it('should work with event component hooks', () => {
799 + it('should work with event listener hooks', () => {
800 const buttonRef = React.createRef();
801 const eventLogs = [];
824 - const EventComponent = createReactEventComponent({
802 + const TestResponder = createEventResponder({
803 targetEventTypes: ['foo'],
804 onEvent: (event, context, props) => {
827 - if (props.onFoo) {
828 - const fooEvent = {
829 - target: event.target,
830 - type: 'foo',
831 - timeStamp: context.getTimeStamp(),
832 - };
833 - context.dispatchEvent(fooEvent, props.onFoo, DiscreteEvent);
834 - }
835 - eventLogs.push(context.isRespondingToHook() ? '[hook]' : '[component]');
805 + const fooEvent = {
806 + target: event.target,
807 + type: 'foo',
808 + timeStamp: context.getTimeStamp(),
809 + };
810 + context.dispatchEvent('onFoo', fooEvent, DiscreteEvent);
811 },
812 });
813
814 const Test = () => {
840 - React.unstable_useEvent(EventComponent, {
815 + React.unstable_useListener(TestResponder, {
816 onFoo: e => eventLogs.push('hook'),
817 });
843 - return (
844 - <EventComponent onFoo={e => eventLogs.push('prop')}>
845 - <button ref={buttonRef} />
846 - </EventComponent>
847 - );
818 +
819 + return <button ref={buttonRef} responders={<TestResponder />} />;
820 };
821
822 ReactDOM.render(<Test />, container);
823 buttonRef.current.dispatchEvent(createEvent('foo'));
852 - expect(eventLogs).toEqual(['[component]', '[hook]', 'prop', 'hook']);
824 + expect(eventLogs).toEqual(['hook']);
825
826 // Clear events
827 eventLogs.length = 0;
828
829 const Test2 = () => {
858 - React.unstable_useEvent(EventComponent, {
830 + React.unstable_useListener(TestResponder, {
831 onFoo: e => eventLogs.push('hook'),
832 });
861 - return <button ref={buttonRef} />;
862 - };
863 -
864 - ReactDOM.render(<Test2 />, container);
865 - buttonRef.current.dispatchEvent(createEvent('foo'));
866 - // No events shold fire, as there are no event components in the branch
867 - expect(eventLogs).toEqual([]);
833
869 - const Test3 = () => {
870 - React.unstable_useEvent(EventComponent, {
871 - onFoo: e => eventLogs.push('hook 2a'),
872 - });
873 - React.unstable_useEvent(EventComponent, {
874 - onFoo: e => eventLogs.push('hook 2b'),
875 - });
876 - return (
877 - <EventComponent onFoo={e => eventLogs.push('should not fire')}>
878 - <EventComponent onFoo={e => eventLogs.push('prop 2')}>
879 - <button ref={buttonRef} />
880 - </EventComponent>
881 - </EventComponent>
882 - );
834 + return <button ref={buttonRef} responders={<TestResponder />} />;
835 };
836
885 - ReactDOM.render(<Test3 />, container);
886 - buttonRef.current.dispatchEvent(createEvent('foo'));
887 - expect(eventLogs).toEqual([
888 - '[component]',
889 - '[hook]',
890 - '[hook]',
891 - 'prop 2',
892 - 'hook 2a',
893 - 'hook 2b',
894 - ]);
837 + ReactDOM.render(<Test2 />, container);
838 + buttonRef.current.dispatchEvent(createEvent('foobar'));
839 });
840 });
packages/react-dom/src/server/ReactPartialRenderer.js
+6 -26
@@ -22,8 +22,8 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
22 import {
23 warnAboutDeprecatedLifecycles,
24 enableSuspenseServerRenderer,
25 - enableFlareAPI,
25 enableFundamentalAPI,
26 + enableFlareAPI,
27 } from 'shared/ReactFeatureFlags';
28
29 import {
@@ -39,7 +39,6 @@ import {
39 REACT_CONTEXT_TYPE,
40 REACT_LAZY_TYPE,
41 REACT_MEMO_TYPE,
42 - REACT_EVENT_COMPONENT_TYPE,
42 REACT_FUNDAMENTAL_TYPE,
43 } from 'shared/ReactSymbols';
44
@@ -344,6 +343,11 @@ const RESERVED_PROPS = {
343 suppressHydrationWarning: null,
344 };
345
346 +if (enableFlareAPI) {
347 + // $FlowFixMe: Flow doesn't like this, it's temp until we remove the flag anyway
348 + RESERVED_PROPS.responders = null;
349 +}
350 +
351 function createOpenTagMarkup(
352 tagVerbatim: string,
353 tagLowercase: string,
@@ -1167,30 +1171,6 @@ class ReactDOMServerRenderer {
1171 this.stack.push(frame);
1172 return '';
1173 }
1170 - case REACT_EVENT_COMPONENT_TYPE: {
1171 - if (enableFlareAPI) {
1172 - const nextChildren = toArray(
1173 - ((nextChild: any): ReactElement).props.children,
1174 - );
1175 - const frame: Frame = {
1176 - type: null,
1177 - domNamespace: parentNamespace,
1178 - children: nextChildren,
1179 - childIndex: 0,
1180 - context: context,
1181 - footer: '',
1182 - };
1183 - if (__DEV__) {
1184 - ((frame: any): FrameDev).debugElementStack = [];
1185 - }
1186 - this.stack.push(frame);
1187 - return '';
1188 - }
1189 - invariant(
1190 - false,
1191 - 'ReactDOMServer does not yet support the event API.',
1192 - );
1193 - }
1174 // eslint-disable-next-line-no-fallthrough
1175 case REACT_FUNDAMENTAL_TYPE: {
1176 if (enableFundamentalAPI) {
packages/react-dom/src/server/ReactPartialRendererHooks.js
+1 -1
@@ -470,5 +470,5 @@ export const Dispatcher: DispatcherType = {
470 useEffect: noop,
471 // Debugging effect
472 useDebugValue: noop,
473 - useEvent: noop,
473 + useListener: noop,
474 };
packages/react-dom/src/shared/ReactControlledValuePropTypes.js
+2 -2
@@ -36,7 +36,7 @@ if (__DEV__) {
36 props.readOnly ||
37 props.disabled ||
38 props[propName] == null ||
39 - enableFlareAPI
39 + (enableFlareAPI && props.responders)
40 ) {
41 return null;
42 }
@@ -53,7 +53,7 @@ if (__DEV__) {
53 props.readOnly ||
54 props.disabled ||
55 props[propName] == null ||
56 - enableFlareAPI
56 + (enableFlareAPI && props.responders)
57 ) {
58 return null;
59 }
packages/react-events/README.md
+26 -37
@@ -3,39 +3,28 @@
3 *This package is experimental. It is intended for use with the experimental React
4 events API that is not available in open source builds.*
5
6 -Event components do not render a host node. They listen to native browser events
6 +Event Responders attach to a host node. They listen to native browser events
7 dispatched on the host node of their child and transform those events into
8 high-level events for applications.
9
10 -The core API is documented below. Documentation for individual Event Components
10 +The core API is documented below. Documentation for individual Event Responders
11 can be found [here](./docs).
12
13 -## EventComponent
13 +## Event Responder Interface
14
15 -An Event Component is defined using `React.unstable_createEvent`:
16 -
17 -```js
18 -const EventComponent = React.unstable_createEvent(
19 - responder: EventResponder,
20 - displayName: string
21 -);
22 -```
23 -
24 -## EventResponder
25 -
26 -An Event Responder is defined using an object. Each responder can define DOM
15 +An Event Responder Interface is defined using an object. Each responder can define DOM
16 events to listen to, handle the synthetic responder events, dispatch custom
17 events, and implement a state machine.
18
19 ```js
20 // types
32 -type ResponderEventType =
33 - | string
34 - | {name: string, passive?: boolean};
21 +type ResponderEventType = string;
22
23 type ResponderEvent = {|
24 nativeEvent: any,
25 + responderTarget: Element | Document,
26 target: Element | Document,
27 + pointerType: string,
28 type: string,
29 passive: boolean,
30 passiveSupported: boolean,
@@ -50,20 +39,20 @@ type CustomEvent = {
39
40 ### getInitialState?: (props: null | Object) => Object
41
53 -The initial state of that the Event Component is created with.
42 +The initial state of that the Event Responder is created with.
43
44 ### onEvent?: (event: ResponderEvent, context: ResponderContext, props, state)
45
46 Called during the bubble phase of the `targetEventTypes` dispatched on DOM
58 -elements within the Event Component.
47 +elements within the Event Responder.
48
49 ### onMount?: (context: ResponderContext, props, state)
50
62 -Called after an Event Component in mounted.
51 +Called after an Event Responder in mounted.
52
53 ### onOwnershipChange?: (context: ResponderContext, props, state)
54
66 -Called when ownership is granted or terminated (either globally or for the responder) for an Event Component instance.
55 +Called when ownership is granted or terminated (either globally or for the responder) for an Event Responder instance.
56
57 ### onRootEvent?: (event: ResponderEvent, context: ResponderContext, props, state)
58
@@ -71,7 +60,7 @@ Called when any of the `rootEventTypes` are dispatched on the root of the app.
60
61 ### onUnmount?: (context: ResponderContext, props, state)
62
74 -Called before an Event Component in unmounted.
63 +Called before an Event Responder in unmounted.
64
65 ### rootEventTypes?: Array<ResponderEventType>
66
@@ -79,7 +68,7 @@ Defines the DOM events to listen to on the root of the app.
68
69 ### targetEventTypes?: Array<ResponderEventType>
70
82 -Defines the DOM events to listen to within the Event Component subtree.
71 +Defines the DOM events to listen to within the Event Responder subtree.
72
73
74 ## ResponderContext
@@ -96,21 +85,21 @@ when it is necessary to do so.
85
86 Clear a timeout defined using `context.setTimeout`.
87
99 -### dispatchEvent(event: CustomEvent, listener, { discrete: boolean })
88 +### dispatchEvent(propName: string, event: CustomEvent, { discrete: boolean })
89
90 Dispatches a custom synthetic event. The `type` and `target` are required
102 -fields, but any other fields can be defined on the `event` that will be passed
103 -to the `listener`. For example:
91 +fields if the event is an object, but any other fields can be defined on the `event` that will be passed
92 +to the `listener`. You can also pass a value that is not an object, but a `boolean`. For example:
93
94 ```js
95 const event = { type: 'press', target, pointerType, x, y };
107 -context.dispatchEvent(event, props.onPress, { discrete: true });
96 +context.dispatchEvent('onPress', event, DiscreteEvent);
97 ```
98
99 ### getFocusableElementsInScope(): Array<Element>
100
101 Returns every DOM element that can be focused within the scope of the Event
113 -Component instance.
102 +Responder instance.
103
104 ### hasOwnership(): boolean
105
@@ -120,18 +109,18 @@ Returns `true` if the instance has taken ownership of the responder.
109
110 Returns `true` if `target` is a child of `element`.
111
123 -### isTargetWithinEventComponent(target: Element): boolean
112 +### isTargetWithinResponder(target: Element): boolean
113
125 -Returns `true` is the target element is within the subtree of the Event Component instance.
114 +Returns `true` is the target element is within the subtree of the Event Responder.
115
127 -### isTargetWithinEventResponderScope(target: Element): boolean
116 +### isTargetWithinResponderScope(target: Element): boolean
117
129 -Returns `true` is the target element is within the current Event Component instance's responder. If the target element
130 -is within the scope of the same responder, but owned by another Event Component instance, this will return `false`.
118 +Returns `true` is the target element is within the current Event Responder's scope. If the target element
119 +is within the scope of the same responder, but owned by another Event Responder instance, this will return `false`.
120
121 ### releaseOwnership(): boolean
122
134 -Returns `true` if the instance released ownership of the Event Component instance.
123 +Returns `true` if the instance released ownership of the Event Responder instance.
124
125 ### removeRootEventTypes(eventTypes: Array<ResponderEventType>)
126
@@ -139,9 +128,9 @@ Remove the root event types added with `addRootEventTypes`.
128
129 ### requestGlobalOwnership(): boolean
130
142 -The current Event Component instance can request global ownership of the event system. When an Event Component instance
131 +The current Event Responder instance can request global ownership of the event system. When an Event Responder instance
132 has global ownership, only that instance and its responder are active. To release ownership to other event responders,
144 -either `releaseOwnership()` must be called or the Event Component instance that had global ownership must be
133 +either `releaseOwnership()` must be called or the Event Responder instance that had global ownership must be
134 unmounted. Calling `requestGlobalOwnership` also returns `true`/`false` if the request was successful.
135
136 ### setTimeout(func: () => void, delay: number): Symbol
packages/react-events/docs/Press.md
+1 -1
@@ -110,7 +110,7 @@ the press point moves more than 10px `onLongPress` is cancelled.
110
111 Called when the element changes long-press state.
112
113 -### onLongPressShouldCancelPress: () => boolean
113 +### longPressShouldCancelPress: () => boolean
114
115 Determines whether calling `onPress` should be cancelled if `onLongPress` or
116 `onLongPressChange` have already been called. Default is `false`.
packages/react-events/focus-scope.js
+1 -3
@@ -9,6 +9,4 @@
9
10 'use strict';
11
12 -const FocusScope = require('./src/dom/FocusScope');
13 -
14 -module.exports = FocusScope.default || FocusScope;
12 +module.exports = require('./src/dom/FocusScope');
packages/react-events/src/dom/Drag.js
+51 -73
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 } from 'shared/ReactDOMTypes';
@@ -20,6 +19,13 @@ import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
19 const targetEventTypes = ['pointerdown'];
20 const rootEventTypes = ['pointerup', 'pointercancel', 'pointermove_active'];
21
22 +type DragListenerProps = {|
23 + onDragStart: (e: DragEvent) => void,
24 + onDragMove: (e: DragEvent) => void,
25 + onDragEnd: (e: DragEvent) => void,
26 + onDragChange: boolean => void,
27 +|};
28 +
29 type DragState = {
30 dragTarget: null | Element | Document,
31 isPointerDown: boolean,
@@ -28,6 +34,7 @@ type DragState = {
34 startY: number,
35 x: number,
36 y: number,
37 + ownershipClaimed: boolean,
38 };
39
40 // In the case we don't have PointerEvents (Safari), we listen to touch events
@@ -72,20 +79,19 @@ function createDragEvent(
79 }
80
81 function dispatchDragEvent(
82 + eventPropName: string,
83 context: ReactDOMResponderContext,
84 name: DragEventType,
77 - listener: DragEvent => void,
85 state: DragState,
86 eventPriority: EventPriority,
87 eventData?: EventData,
88 ): void {
89 const target = ((state.dragTarget: any): Element | Document);
90 const syntheticEvent = createDragEvent(context, name, target, eventData);
84 - context.dispatchEvent(syntheticEvent, listener, eventPriority);
91 + context.dispatchEvent(eventPropName, syntheticEvent, eventPriority);
92 }
93
87 -const DragResponder: ReactDOMEventResponder = {
88 - displayName: 'Drag',
94 +const dragResponderImpl = {
95 targetEventTypes,
96 getInitialState(): DragState {
97 return {
@@ -96,6 +102,7 @@ const DragResponder: ReactDOMEventResponder = {
102 startY: 0,
103 x: 0,
104 y: 0,
105 + ownershipClaimed: false,
106 };
107 },
108 onEvent(
@@ -111,9 +118,6 @@ const DragResponder: ReactDOMEventResponder = {
118 case 'mousedown':
119 case 'pointerdown': {
120 if (!state.isDragging) {
114 - if (props.onShouldClaimOwnership) {
115 - context.releaseOwnership();
116 - }
121 const obj =
122 type === 'touchstart'
123 ? (nativeEvent: any).changedTouches[0]
@@ -125,16 +129,13 @@ const DragResponder: ReactDOMEventResponder = {
129 state.dragTarget = target;
130 state.isPointerDown = true;
131
128 - if (props.onDragStart) {
129 - dispatchDragEvent(
130 - context,
131 - 'dragstart',
132 - props.onDragStart,
133 - state,
134 - DiscreteEvent,
135 - );
136 - }
137 -
132 + dispatchDragEvent(
133 + 'onDragStart',
134 + context,
135 + 'dragstart',
136 + state,
137 + DiscreteEvent,
138 + );
139 context.addRootEventTypes(rootEventTypes);
140 }
141 break;
@@ -171,46 +172,33 @@ const DragResponder: ReactDOMEventResponder = {
172 if (!state.isDragging) {
173 let shouldEnableDragging = true;
174
174 - if (
175 - props.onShouldClaimOwnership &&
176 - props.onShouldClaimOwnership()
177 - ) {
175 + if (props.shouldClaimOwnership && props.shouldClaimOwnership()) {
176 shouldEnableDragging = context.requestGlobalOwnership();
177 + if (shouldEnableDragging) {
178 + state.ownershipClaimed = true;
179 + }
180 }
181 if (shouldEnableDragging) {
182 state.isDragging = true;
182 - if (props.onDragChange) {
183 - const dragChangeEventListener = () => {
184 - props.onDragChange(true);
185 - };
186 - dispatchDragEvent(
187 - context,
188 - 'dragchange',
189 - dragChangeEventListener,
190 - state,
191 - UserBlockingEvent,
192 - );
193 - }
183 + context.dispatchEvent('onDragChange', true, UserBlockingEvent);
184 } else {
185 state.dragTarget = null;
186 state.isPointerDown = false;
187 context.removeRootEventTypes(rootEventTypes);
188 }
189 } else {
200 - if (props.onDragMove) {
201 - const eventData = {
202 - diffX: x - state.startX,
203 - diffY: y - state.startY,
204 - };
205 - dispatchDragEvent(
206 - context,
207 - 'dragmove',
208 - props.onDragMove,
209 - state,
210 - UserBlockingEvent,
211 - eventData,
212 - );
213 - }
190 + const eventData = {
191 + diffX: x - state.startX,
192 + diffY: y - state.startY,
193 + };
194 + dispatchDragEvent(
195 + 'onDragMove',
196 + context,
197 + 'dragmove',
198 + state,
199 + UserBlockingEvent,
200 + eventData,
201 + );
202 (nativeEvent: any).preventDefault();
203 }
204 }
@@ -222,30 +210,17 @@ const DragResponder: ReactDOMEventResponder = {
210 case 'mouseup':
211 case 'pointerup': {
212 if (state.isDragging) {
225 - if (props.onShouldClaimOwnership) {
213 + if (state.ownershipClaimed) {
214 context.releaseOwnership();
215 }
228 - if (props.onDragEnd) {
229 - dispatchDragEvent(
230 - context,
231 - 'dragend',
232 - props.onDragEnd,
233 - state,
234 - DiscreteEvent,
235 - );
236 - }
237 - if (props.onDragChange) {
238 - const dragChangeEventListener = () => {
239 - props.onDragChange(false);
240 - };
241 - dispatchDragEvent(
242 - context,
243 - 'dragchange',
244 - dragChangeEventListener,
245 - state,
246 - UserBlockingEvent,
247 - );
248 - }
216 + dispatchDragEvent(
217 + 'onDragEnd',
218 + context,
219 + 'dragend',
220 + state,
221 + DiscreteEvent,
222 + );
223 + context.dispatchEvent('onDragChange', false, UserBlockingEvent);
224 state.isDragging = false;
225 }
226 if (state.isPointerDown) {
@@ -259,8 +234,11 @@ const DragResponder: ReactDOMEventResponder = {
234 },
235 };
236
262 -export const Drag = React.unstable_createEvent(DragResponder);
237 +export const DragResponder = React.unstable_createResponder(
238 + 'Drag',
239 + dragResponderImpl,
240 +);
241
264 -export function useDrag(props: Object): void {
265 - React.unstable_useEvent(Drag, props);
242 +export function useDragListener(props: DragListenerProps): void {
243 + React.unstable_useListener(DragResponder, props);
244 }
packages/react-events/src/dom/Focus.js
+42 -105
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 PointerType,
@@ -35,20 +34,26 @@ type FocusState = {
34 pointerType: PointerType,
35 };
36
38 -type FocusProps = {
39 - disabled: boolean,
37 +type FocusListenerProps = {|
38 onBlur: (e: FocusEvent) => void,
39 onFocus: (e: FocusEvent) => void,
40 onFocusChange: boolean => void,
41 onFocusVisibleChange: boolean => void,
42 +|};
43 +
44 +type FocusProps = {
45 + disabled: boolean,
46 };
47
48 type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
49
48 -type FocusWithinProps = {
49 - disabled: boolean,
50 +type FocusWithinListenerProps = {|
51 onFocusWithinChange: boolean => void,
52 onFocusWithinVisibleChange: boolean => void,
53 +|};
54 +
55 +type FocusWithinProps = {
56 + disabled: boolean,
57 };
58
59 type FocusWithinEventType = 'focuswithinvisiblechange' | 'focuswithinchange';
@@ -190,29 +195,16 @@ function dispatchFocusEvents(
195 ) {
196 const pointerType = state.pointerType;
197 const target = ((state.focusTarget: any): Element | Document);
193 - if (props.onFocus) {
194 - const syntheticEvent = createFocusEvent(
195 - context,
196 - 'focus',
197 - target,
198 - pointerType,
199 - );
200 - context.dispatchEvent(syntheticEvent, props.onFocus, DiscreteEvent);
201 - }
202 - if (props.onFocusChange) {
203 - const listener = () => {
204 - props.onFocusChange(true);
205 - };
206 - const syntheticEvent = createFocusEvent(
207 - context,
208 - 'focuschange',
209 - target,
210 - pointerType,
211 - );
212 - context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
213 - }
198 + const syntheticEvent = createFocusEvent(
199 + context,
200 + 'focus',
201 + target,
202 + pointerType,
203 + );
204 + context.dispatchEvent('onFocus', syntheticEvent, DiscreteEvent);
205 + context.dispatchEvent('onFocusChange', true, DiscreteEvent);
206 if (state.isFocusVisible) {
215 - dispatchFocusVisibleChangeEvent(context, props, state, true);
207 + dispatchFocusVisibleChangeEvent(context, true);
208 }
209 }
210
@@ -223,52 +215,19 @@ function dispatchBlurEvents(
215 ) {
216 const pointerType = state.pointerType;
217 const target = ((state.focusTarget: any): Element | Document);
226 - if (props.onBlur) {
227 - const syntheticEvent = createFocusEvent(
228 - context,
229 - 'blur',
230 - target,
231 - pointerType,
232 - );
233 - context.dispatchEvent(syntheticEvent, props.onBlur, DiscreteEvent);
234 - }
235 - if (props.onFocusChange) {
236 - const listener = () => {
237 - props.onFocusChange(false);
238 - };
239 - const syntheticEvent = createFocusEvent(
240 - context,
241 - 'focuschange',
242 - target,
243 - pointerType,
244 - );
245 - context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
246 - }
218 + const syntheticEvent = createFocusEvent(context, 'blur', target, pointerType);
219 + context.dispatchEvent('onBlur', syntheticEvent, DiscreteEvent);
220 + context.dispatchEvent('onFocusChange', false, DiscreteEvent);
221 if (state.isFocusVisible) {
248 - dispatchFocusVisibleChangeEvent(context, props, state, false);
222 + dispatchFocusVisibleChangeEvent(context, false);
223 }
224 }
225
226 function dispatchFocusVisibleChangeEvent(
227 context: ReactDOMResponderContext,
254 - props: FocusProps,
255 - state: FocusState,
228 value: boolean,
229 ) {
258 - const pointerType = state.pointerType;
259 - const target = ((state.focusTarget: any): Element | Document);
260 - if (props.onFocusVisibleChange) {
261 - const listener = () => {
262 - props.onFocusVisibleChange(value);
263 - };
264 - const syntheticEvent = createFocusEvent(
265 - context,
266 - 'focusvisiblechange',
267 - target,
268 - pointerType,
269 - );
270 - context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
271 - }
230 + context.dispatchEvent('onFocusVisibleChange', value, DiscreteEvent);
231 }
232
233 function unmountFocusResponder(
@@ -281,8 +240,7 @@ function unmountFocusResponder(
240 }
241 }
242
284 -const FocusResponder: ReactDOMEventResponder = {
285 - displayName: 'Focus',
243 +const focusResponderImpl = {
244 targetEventTypes,
245 rootEventTypes,
246 getInitialState(): FocusState {
@@ -341,7 +299,7 @@ const FocusResponder: ReactDOMEventResponder = {
299 handleRootEvent(event, context, state, isFocusVisible => {
300 if (state.isFocusVisible !== isFocusVisible) {
301 state.isFocusVisible = isFocusVisible;
344 - dispatchFocusVisibleChangeEvent(context, props, state, isFocusVisible);
302 + dispatchFocusVisibleChangeEvent(context, isFocusVisible);
303 }
304 });
305 },
@@ -361,10 +319,13 @@ const FocusResponder: ReactDOMEventResponder = {
319 },
320 };
321
364 -export const Focus = React.unstable_createEvent(FocusResponder);
322 +export const FocusResponder = React.unstable_createResponder(
323 + 'Focus',
324 + focusResponderImpl,
325 +);
326
366 -export function useFocus(props: FocusProps): void {
367 - React.unstable_useEvent(Focus, props);
327 +export function useFocusListener(props: FocusListenerProps): void {
328 + React.unstable_useListener(FocusResponder, props);
329 }
330
331 /**
@@ -377,20 +338,7 @@ function dispatchFocusWithinChangeEvent(
338 state: FocusState,
339 value: boolean,
340 ) {
380 - const pointerType = state.pointerType;
381 - const target = ((state.focusTarget: any): Element | Document);
382 - if (props.onFocusWithinChange) {
383 - const listener = function() {
384 - props.onFocusWithinChange(value);
385 - };
386 - const syntheticEvent = createFocusEvent(
387 - context,
388 - 'focuswithinchange',
389 - target,
390 - pointerType,
391 - );
392 - context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
393 - }
341 + context.dispatchEvent('onFocusWithinChange', value, DiscreteEvent);
342 if (state.isFocusVisible) {
343 dispatchFocusWithinVisibleChangeEvent(context, props, state, value);
344 }
@@ -402,20 +350,7 @@ function dispatchFocusWithinVisibleChangeEvent(
350 state: FocusState,
351 value: boolean,
352 ) {
405 - const pointerType = state.pointerType;
406 - const target = ((state.focusTarget: any): Element | Document);
407 - if (props.onFocusWithinVisibleChange) {
408 - const listener = function() {
409 - props.onFocusWithinVisibleChange(value);
410 - };
411 - const syntheticEvent = createFocusEvent(
412 - context,
413 - 'focuswithinvisiblechange',
414 - target,
415 - pointerType,
416 - );
417 - context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
418 - }
353 + context.dispatchEvent('onFocusWithinVisibleChange', value, DiscreteEvent);
354 }
355
356 function unmountFocusWithinResponder(
@@ -428,8 +363,7 @@ function unmountFocusWithinResponder(
363 }
364 }
365
431 -const FocusWithinResponder: ReactDOMEventResponder = {
432 - displayName: 'FocusWithin',
366 +const focusWithinResponderImpl = {
367 targetEventTypes,
368 rootEventTypes,
369 getInitialState(): FocusState {
@@ -477,7 +411,7 @@ const FocusWithinResponder: ReactDOMEventResponder = {
411 case 'blur': {
412 if (
413 state.isFocused &&
480 - !context.isTargetWithinEventResponderScope(relatedTarget)
414 + !context.isTargetWithinResponder(relatedTarget)
415 ) {
416 dispatchFocusWithinChangeEvent(context, props, state, false);
417 state.isFocused = false;
@@ -520,8 +454,11 @@ const FocusWithinResponder: ReactDOMEventResponder = {
454 },
455 };
456
523 -export const FocusWithin = React.unstable_createEvent(FocusWithinResponder);
457 +export const FocusWithinResponder = React.unstable_createResponder(
458 + 'FocusWithin',
459 + focusWithinResponderImpl,
460 +);
461
525 -export function useFocusWithin(props: FocusWithinProps): void {
526 - React.unstable_useEvent(FocusWithin, props);
462 +export function useFocusWithinListener(props: FocusWithinListenerProps): void {
463 + React.unstable_useListener(FocusWithinResponder, props);
464 }
packages/react-events/src/dom/FocusScope.js
+17 -11
@@ -7,7 +7,6 @@
7 * @flow
8 */
9 import type {
10 - ReactDOMEventResponder,
10 ReactDOMResponderEvent,
11 ReactDOMResponderContext,
12 } from 'shared/ReactDOMTypes';
@@ -40,14 +39,13 @@ function getFirstFocusableElement(
39 context: ReactDOMResponderContext,
40 state: FocusScopeState,
41 ): ?HTMLElement {
43 - const elements = context.getFocusableElementsInScope();
42 + const elements = context.getFocusableElementsInScope(false);
43 if (elements.length > 0) {
44 return elements[0];
45 }
46 }
47
49 -const FocusScopeResponder: ReactDOMEventResponder = {
50 - displayName: 'FocusScope',
48 +const focusScopeResponderImpl = {
49 targetEventTypes,
50 rootEventTypes,
51 getInitialState(): FocusScopeState {
@@ -68,14 +66,14 @@ const FocusScopeResponder: ReactDOMEventResponder = {
66 const focusedElement = context.getActiveDocument().activeElement;
67 if (
68 focusedElement !== null &&
71 - context.isTargetWithinEventComponent(focusedElement)
69 + context.isTargetWithinResponder(focusedElement)
70 ) {
71 const {altkey, ctrlKey, metaKey, shiftKey} = (nativeEvent: any);
72 // Skip if any of these keys are being pressed
73 if (altkey || ctrlKey || metaKey) {
74 return;
75 }
78 - const elements = context.getFocusableElementsInScope();
76 + const elements = context.getFocusableElementsInScope(false);
77 const position = elements.indexOf(focusedElement);
78 const lastPosition = elements.length - 1;
79 let nextElement = null;
@@ -86,7 +84,10 @@ const FocusScopeResponder: ReactDOMEventResponder = {
84 nextElement = elements[lastPosition];
85 } else {
86 // Out of bounds
89 - context.continueLocalPropagation();
87 + // TODO remove this, this is hacky
88 + const allElements = context.getFocusableElementsInScope(true);
89 + const prevPosition = allElements.indexOf(focusedElement) - 1;
90 + nextElement = allElements[prevPosition] || null;
91 return;
92 }
93 } else {
@@ -98,8 +99,10 @@ const FocusScopeResponder: ReactDOMEventResponder = {
99 nextElement = elements[0];
100 } else {
101 // Out of bounds
101 - context.continueLocalPropagation();
102 - return;
102 + // TODO remove this, this is hacky
103 + const allElements = context.getFocusableElementsInScope(true);
104 + const nextPosition = allElements.indexOf(focusedElement) + 1;
105 + nextElement = allElements[nextPosition] || null;
106 }
107 } else {
108 nextElement = elements[position + 1];
@@ -123,7 +126,7 @@ const FocusScopeResponder: ReactDOMEventResponder = {
126
127 // Handle global focus containment
128 if (props.contain) {
126 - if (!context.isTargetWithinEventComponent(target)) {
129 + if (!context.isTargetWithinResponder(target)) {
130 const currentFocusedNode = state.currentFocusedNode;
131 if (currentFocusedNode !== null) {
132 focusElement(currentFocusedNode);
@@ -158,4 +161,7 @@ const FocusScopeResponder: ReactDOMEventResponder = {
161 },
162 };
163
161 -export default React.unstable_createEvent(FocusScopeResponder);
164 +export const FocusScopeResponder = React.unstable_createResponder(
165 + 'FocusSocpe',
166 + focusScopeResponderImpl,
167 +);
packages/react-events/src/dom/Hover.js
+35 -61
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 } from 'shared/ReactDOMTypes';
@@ -16,14 +15,17 @@ import type {
15 import React from 'react';
16 import {UserBlockingEvent} from 'shared/ReactTypes';
17
19 -type HoverProps = {
20 - disabled: boolean,
21 - delayHoverEnd: number,
22 - delayHoverStart: number,
18 +type HoverListenerProps = {|
19 onHoverChange: boolean => void,
20 onHoverEnd: (e: HoverEvent) => void,
21 onHoverMove: (e: HoverEvent) => void,
22 onHoverStart: (e: HoverEvent) => void,
23 +|};
24 +
25 +type HoverProps = {
26 + disabled: boolean,
27 + delayHoverEnd: number,
28 + delayHoverStart: number,
29 preventDefault: boolean,
30 };
31
@@ -108,16 +110,7 @@ function dispatchHoverChangeEvent(
110 state: HoverState,
111 ): void {
112 const bool = state.isActiveHovered;
111 - const listener = () => {
112 - props.onHoverChange(bool);
113 - };
114 - const syntheticEvent = createHoverEvent(
115 - event,
116 - context,
117 - 'hoverchange',
118 - ((state.hoverTarget: any): Element | Document),
119 - );
120 - context.dispatchEvent(syntheticEvent, listener, UserBlockingEvent);
113 + context.dispatchEvent('onHoverChange', bool, UserBlockingEvent);
114 }
115
116 function dispatchHoverStartEvents(
@@ -130,9 +123,7 @@ function dispatchHoverStartEvents(
123 if (event !== null) {
124 const {nativeEvent} = event;
125 if (
133 - context.isTargetWithinEventResponderScope(
134 - (nativeEvent: any).relatedTarget,
135 - )
126 + context.isTargetWithinResponderScope((nativeEvent: any).relatedTarget)
127 ) {
128 return;
129 }
@@ -147,23 +138,14 @@ function dispatchHoverStartEvents(
138
139 const activate = () => {
140 state.isActiveHovered = true;
150 -
151 - if (props.onHoverStart) {
152 - const syntheticEvent = createHoverEvent(
153 - event,
154 - context,
155 - 'hoverstart',
156 - ((target: any): Element | Document),
157 - );
158 - context.dispatchEvent(
159 - syntheticEvent,
160 - props.onHoverStart,
161 - UserBlockingEvent,
162 - );
163 - }
164 - if (props.onHoverChange) {
165 - dispatchHoverChangeEvent(event, context, props, state);
166 - }
141 + const syntheticEvent = createHoverEvent(
142 + event,
143 + context,
144 + 'hoverstart',
145 + ((target: any): Element | Document),
146 + );
147 + context.dispatchEvent('onHoverStart', syntheticEvent, UserBlockingEvent);
148 + dispatchHoverChangeEvent(event, context, props, state);
149 };
150
151 if (!state.isActiveHovered) {
@@ -193,9 +175,7 @@ function dispatchHoverEndEvents(
175 if (event !== null) {
176 const {nativeEvent} = event;
177 if (
196 - context.isTargetWithinEventResponderScope(
197 - (nativeEvent: any).relatedTarget,
198 - )
178 + context.isTargetWithinResponderScope((nativeEvent: any).relatedTarget)
179 ) {
180 return;
181 }
@@ -211,22 +191,14 @@ function dispatchHoverEndEvents(
191 const deactivate = () => {
192 state.isActiveHovered = false;
193
214 - if (props.onHoverEnd) {
215 - const syntheticEvent = createHoverEvent(
216 - event,
217 - context,
218 - 'hoverend',
219 - ((target: any): Element | Document),
220 - );
221 - context.dispatchEvent(
222 - syntheticEvent,
223 - props.onHoverEnd,
224 - UserBlockingEvent,
225 - );
226 - }
227 - if (props.onHoverChange) {
228 - dispatchHoverChangeEvent(event, context, props, state);
229 - }
194 + const syntheticEvent = createHoverEvent(
195 + event,
196 + context,
197 + 'hoverend',
198 + ((target: any): Element | Document),
199 + );
200 + context.dispatchEvent('onHoverEnd', syntheticEvent, UserBlockingEvent);
201 + dispatchHoverChangeEvent(event, context, props, state);
202 state.hoverTarget = null;
203 state.ignoreEmulatedMouseEvents = false;
204 state.isTouched = false;
@@ -271,8 +243,7 @@ function isEmulatedMouseEvent(event, state) {
243 );
244 }
245
274 -const HoverResponder: ReactDOMEventResponder = {
275 - displayName: 'Hover',
246 +const hoverResponderImpl = {
247 targetEventTypes,
248 getInitialState() {
249 return {
@@ -332,7 +303,7 @@ const HoverResponder: ReactDOMEventResponder = {
303 case 'pointermove':
304 case 'mousemove': {
305 if (state.isHovered && !isEmulatedMouseEvent(event, state)) {
335 - if (props.onHoverMove && state.hoverTarget !== null) {
306 + if (state.hoverTarget !== null) {
307 const syntheticEvent = createHoverEvent(
308 event,
309 context,
@@ -340,8 +311,8 @@ const HoverResponder: ReactDOMEventResponder = {
311 state.hoverTarget,
312 );
313 context.dispatchEvent(
314 + 'onHoverMove',
315 syntheticEvent,
344 - props.onHoverMove,
316 UserBlockingEvent,
317 );
318 }
@@ -382,8 +353,11 @@ const HoverResponder: ReactDOMEventResponder = {
353 },
354 };
355
385 -export const Hover = React.unstable_createEvent(HoverResponder);
356 +export const HoverResponder = React.unstable_createResponder(
357 + 'Hover',
358 + hoverResponderImpl,
359 +);
360
387 -export function useHover(props: HoverProps): void {
388 - React.unstable_useEvent(Hover, props);
361 +export function useHoverListener(props: HoverListenerProps): void {
362 + React.unstable_useListener(HoverResponder, props);
363 }
packages/react-events/src/dom/Input.js
+17 -25
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 } from 'shared/ReactDOMTypes';
@@ -18,11 +17,14 @@ import {DiscreteEvent} from 'shared/ReactTypes';
17
18 type InputEventType = 'change' | 'beforechange' | 'valuechange';
19
21 -type InputResponderProps = {
22 - disabled: boolean,
20 +type InputListenerProps = {|
21 onBeforeChange: (e: InputEvent) => void,
22 onChange: (e: InputEvent) => void,
23 onValueChange: (value: string | boolean) => void,
24 +|};
25 +
26 +type InputResponderProps = {
27 + disabled: boolean,
28 };
29
30 type InputEvent = {|
@@ -74,14 +76,14 @@ function createInputEvent(
76 }
77
78 function dispatchInputEvent(
79 + eventPropName: string,
80 event: ReactDOMResponderEvent,
81 context: ReactDOMResponderContext,
82 type: InputEventType,
80 - listener: (e: any) => void,
83 target: Element | Document,
84 ): void {
85 const syntheticEvent = createInputEvent(event, context, type, target);
84 - context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
86 + context.dispatchEvent(eventPropName, syntheticEvent, DiscreteEvent);
87 }
88
89 function getNodeName(elem: Element | Document): string {
@@ -112,17 +114,11 @@ function shouldUseChangeEvent(elem: Element | Document): boolean {
114 }
115
116 function dispatchChangeEvent(
115 - event: ReactDOMResponderEvent,
117 context: ReactDOMResponderContext,
117 - type: InputEventType,
118 - changeListener: (e: string | boolean) => void,
118 target: Element | Document,
119 ): void {
120 const value = getValueFromNode(target);
122 - const listener = () => {
123 - changeListener(value);
124 - };
125 - dispatchInputEvent(event, context, type, listener, target);
121 + context.dispatchEvent('onValueChange', value, DiscreteEvent);
122 }
123
124 function dispatchBothChangeEvents(
@@ -131,15 +127,9 @@ function dispatchBothChangeEvents(
127 props: InputResponderProps,
128 target: Document | Element,
129 ): void {
134 - const onChange = props.onChange;
130 context.enqueueStateRestore(target);
136 - if (onChange) {
137 - dispatchInputEvent(event, context, 'change', onChange, target);
138 - }
139 - const onValueChange = props.onValueChange;
140 - if (onValueChange) {
141 - dispatchChangeEvent(event, context, 'valuechange', onValueChange, target);
142 - }
131 + dispatchInputEvent('onChange', event, context, 'change', target);
132 + dispatchChangeEvent(context, target);
133 }
134
135 function updateValueIfChanged(elem: Element | Document): boolean {
@@ -173,8 +163,7 @@ function getValueFromNode(node: Element | Document): string {
163 return value;
164 }
165
176 -const InputResponder: ReactDOMEventResponder = {
177 - displayName: 'Input',
166 +const inputResponderImpl = {
167 targetEventTypes,
168 onEvent(
169 event: ReactDOMResponderEvent,
@@ -212,8 +201,11 @@ const InputResponder: ReactDOMEventResponder = {
201 },
202 };
203
215 -export const Input = React.unstable_createEvent(InputResponder);
204 +export const InputResponder = React.unstable_createResponder(
205 + 'Input',
206 + inputResponderImpl,
207 +);
208
217 -export function useInput(props: InputResponderProps): void {
218 - React.unstable_useEvent(Input, props);
209 +export function useInputListener(props: InputListenerProps): void {
210 + React.unstable_useListener(InputResponder, props);
211 }
packages/react-events/src/dom/Press.js
+73 -141
@@ -8,31 +8,31 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 PointerType,
14 } from 'shared/ReactDOMTypes';
15 import type {EventPriority} from 'shared/ReactTypes';
17 -import warning from 'shared/warning';
16
17 import React from 'react';
18 import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
19
22 -type PressProps = {
23 - disabled: boolean,
24 - delayLongPress: number,
25 - delayPressEnd: number,
26 - delayPressStart: number,
20 +type PressListenerProps = {|
21 onContextMenu: (e: PressEvent) => void,
22 onLongPress: (e: PressEvent) => void,
23 onLongPressChange: boolean => void,
30 - onLongPressShouldCancelPress: () => boolean,
24 onPress: (e: PressEvent) => void,
25 onPressChange: boolean => void,
26 onPressEnd: (e: PressEvent) => void,
27 onPressMove: (e: PressEvent) => void,
28 onPressStart: (e: PressEvent) => void,
29 +|};
30 +
31 +type PressProps = {|
32 + disabled: boolean,
33 + delayLongPress: number,
34 + delayPressEnd: number,
35 + delayPressStart: number,
36 pressRetentionOffset: {
37 top: number,
38 right: number,
@@ -42,7 +42,9 @@ type PressProps = {
42 preventContextMenu: boolean,
43 preventDefault: boolean,
44 stopPropagation: boolean,
45 -};
45 + enableLongPress: boolean,
46 + longPressShouldCancelPress: () => boolean,
47 +|};
48
49 type PressState = {
50 activationPosition: null | $ReadOnly<{|
@@ -215,11 +217,11 @@ function createPressEvent(
217 }
218
219 function dispatchEvent(
220 + eventPropName: string,
221 event: ?ReactDOMResponderEvent,
222 context: ReactDOMResponderContext,
223 state: PressState,
224 name: PressEventType,
222 - listener: (e: Object) => void,
225 eventPriority: EventPriority,
226 ): void {
227 const target = ((state.pressTarget: any): Element | Document);
@@ -237,40 +239,23 @@ function dispatchEvent(
239 touchEvent,
240 defaultPrevented,
241 );
240 - context.dispatchEvent(syntheticEvent, listener, eventPriority);
242 + context.dispatchEvent(eventPropName, syntheticEvent, eventPriority);
243 }
244
245 function dispatchPressChangeEvent(
244 - event: ?ReactDOMResponderEvent,
246 context: ReactDOMResponderContext,
246 - props: PressProps,
247 state: PressState,
248 ): void {
249 const bool = state.isActivePressed;
250 - const listener = () => {
251 - props.onPressChange(bool);
252 - };
253 - dispatchEvent(event, context, state, 'presschange', listener, DiscreteEvent);
250 + context.dispatchEvent('onPressChange', bool, DiscreteEvent);
251 }
252
253 function dispatchLongPressChangeEvent(
257 - event: ?ReactDOMResponderEvent,
254 context: ReactDOMResponderContext,
259 - props: PressProps,
255 state: PressState,
256 ): void {
257 const bool = state.isLongPressed;
263 - const listener = () => {
264 - props.onLongPressChange(bool);
265 - };
266 - dispatchEvent(
267 - event,
268 - context,
269 - state,
270 - 'longpresschange',
271 - listener,
272 - DiscreteEvent,
273 - );
258 + context.dispatchEvent('onLongPressChange', bool, DiscreteEvent);
259 }
260
261 function activate(event: ReactDOMResponderEvent, context, props, state) {
@@ -282,18 +267,16 @@ function activate(event: ReactDOMResponderEvent, context, props, state) {
267 state.activationPosition = {x, y};
268 }
269
285 - if (props.onPressStart) {
286 - dispatchEvent(
287 - event,
288 - context,
289 - state,
290 - 'pressstart',
291 - props.onPressStart,
292 - DiscreteEvent,
293 - );
294 - }
295 - if (!wasActivePressed && props.onPressChange) {
296 - dispatchPressChangeEvent(event, context, props, state);
270 + dispatchEvent(
271 + 'onPressStart',
272 + event,
273 + context,
274 + state,
275 + 'pressstart',
276 + DiscreteEvent,
277 + );
278 + if (!wasActivePressed) {
279 + dispatchPressChangeEvent(context, state);
280 }
281 }
282
@@ -302,21 +285,10 @@ function deactivate(event: ?ReactDOMResponderEvent, context, props, state) {
285 state.isActivePressed = false;
286 state.isLongPressed = false;
287
305 - if (props.onPressEnd) {
306 - dispatchEvent(
307 - event,
308 - context,
309 - state,
310 - 'pressend',
311 - props.onPressEnd,
312 - DiscreteEvent,
313 - );
314 - }
315 - if (props.onPressChange) {
316 - dispatchPressChangeEvent(event, context, props, state);
317 - }
318 - if (wasLongPressed && props.onLongPressChange) {
319 - dispatchLongPressChangeEvent(event, context, props, state);
288 + dispatchEvent('onPressEnd', event, context, state, 'pressend', DiscreteEvent);
289 + dispatchPressChangeEvent(context, state);
290 + if (wasLongPressed && props.enableLongPress) {
291 + dispatchLongPressChangeEvent(context, state);
292 }
293 }
294
@@ -337,10 +309,7 @@ function dispatchPressStartEvents(
309 state.isActivePressStart = true;
310 activate(event, context, props, state);
311
340 - if (
341 - (props.onLongPress || props.onLongPressChange) &&
342 - !state.isLongPressed
343 - ) {
312 + if (!state.isLongPressed && props.enableLongPress) {
313 const delayLongPress = calculateDelayMS(
314 props.delayLongPress,
315 10,
@@ -349,19 +318,15 @@ function dispatchPressStartEvents(
318 state.longPressTimeout = context.setTimeout(() => {
319 state.isLongPressed = true;
320 state.longPressTimeout = null;
352 - if (props.onLongPress) {
353 - dispatchEvent(
354 - event,
355 - context,
356 - state,
357 - 'longpress',
358 - props.onLongPress,
359 - DiscreteEvent,
360 - );
361 - }
362 - if (props.onLongPressChange) {
363 - dispatchLongPressChangeEvent(event, context, props, state);
364 - }
321 + dispatchEvent(
322 + 'onLongPress',
323 + event,
324 + context,
325 + state,
326 + 'longpress',
327 + DiscreteEvent,
328 + );
329 + dispatchLongPressChangeEvent(context, state);
330 }, delayLongPress);
331 }
332 };
@@ -613,14 +578,7 @@ function handleStopPropagation(
578 nativeEvent,
579 ): void {
580 const stopPropagation = props.stopPropagation;
616 - if (stopPropagation !== undefined && context.isRespondingToHook()) {
617 - if (__DEV__) {
618 - warning(
619 - false,
620 - '"stopPropagation" prop cannot be passed to Press event hooks. This will result in a no-op.',
621 - );
622 - }
623 - } else if (stopPropagation === true) {
581 + if (stopPropagation === true) {
582 nativeEvent.stopPropagation();
583 }
584 }
@@ -630,8 +588,7 @@ function targetIsDocument(target: null | Node): boolean {
588 return target === null || target.nodeType === 9;
589 }
590
633 -const PressResponder: ReactDOMEventResponder = {
634 - displayName: 'Press',
591 +const pressResponderImpl = {
592 targetEventTypes,
593 getInitialState(): PressState {
594 return {
@@ -766,14 +723,7 @@ const PressResponder: ReactDOMEventResponder = {
723 case 'contextmenu': {
724 const preventContextMenu = props.preventContextMenu;
725
769 - if (preventContextMenu !== undefined && context.isRespondingToHook()) {
770 - if (__DEV__) {
771 - warning(
772 - false,
773 - '"preventContextMenu" prop cannot be passed to Press event hooks. This will result in a no-op.',
774 - );
775 - }
776 - } else if (preventContextMenu === true) {
726 + if (preventContextMenu === true) {
727 // Skip dispatching of onContextMenu below
728 nativeEvent.preventDefault();
729 }
@@ -781,34 +731,21 @@ const PressResponder: ReactDOMEventResponder = {
731 if (isPressed) {
732 const preventDefault = props.preventDefault;
733
784 - if (preventDefault !== undefined && context.isRespondingToHook()) {
785 - if (__DEV__) {
786 - warning(
787 - false,
788 - '"preventDefault" prop cannot be passed to Press event hooks. This will result in a no-op.',
789 - );
790 - }
791 - } else if (
792 - preventDefault !== false &&
793 - !nativeEvent.defaultPrevented
794 - ) {
734 + if (preventDefault !== false && !nativeEvent.defaultPrevented) {
735 // Skip dispatching of onContextMenu below
736 nativeEvent.preventDefault();
737 return;
738 }
739 dispatchCancel(event, context, props, state);
740 }
801 -
802 - if (props.onContextMenu) {
803 - dispatchEvent(
804 - event,
805 - context,
806 - state,
807 - 'contextmenu',
808 - props.onContextMenu,
809 - DiscreteEvent,
810 - );
811 - }
741 + dispatchEvent(
742 + 'onContextMenu',
743 + event,
744 + context,
745 + state,
746 + 'contextmenu',
747 + DiscreteEvent,
748 + );
749 // Click won't occur, so we need to remove root events
750 removeRootEventTypes(context, state);
751 break;
@@ -878,16 +815,14 @@ const PressResponder: ReactDOMEventResponder = {
815
816 if (state.isPressWithinResponderRegion) {
817 if (isPressed) {
881 - if (props.onPressMove) {
882 - dispatchEvent(
883 - event,
884 - context,
885 - state,
886 - 'pressmove',
887 - props.onPressMove,
888 - UserBlockingEvent,
889 - );
890 - }
818 + dispatchEvent(
819 + 'onPressMove',
820 + event,
821 + context,
822 + state,
823 + 'pressmove',
824 + UserBlockingEvent,
825 + );
826 if (
827 state.activationPosition != null &&
828 state.longPressTimeout != null
@@ -942,8 +877,8 @@ const PressResponder: ReactDOMEventResponder = {
877
878 // Determine whether to call preventDefault on subsequent native events.
879 if (
945 - context.isTargetWithinEventComponent(target) &&
946 - context.isTargetWithinHostComponent(target, 'a', true)
880 + context.isTargetWithinResponder(target) &&
881 + context.isTargetWithinHostComponent(target, 'a')
882 ) {
883 const {
884 altKey,
@@ -954,14 +889,7 @@ const PressResponder: ReactDOMEventResponder = {
889 // Check "open in new window/tab" and "open context menu" key modifiers
890 const preventDefault = props.preventDefault;
891
957 - if (preventDefault !== undefined && context.isRespondingToHook()) {
958 - if (__DEV__) {
959 - warning(
960 - false,
961 - '"preventDefault" prop cannot be passed to Press event hooks. This will result in a no-op.',
962 - );
963 - }
964 - } else if (
892 + if (
893 preventDefault !== false &&
894 !shiftKey &&
895 !metaKey &&
@@ -976,7 +904,7 @@ const PressResponder: ReactDOMEventResponder = {
904 const pressTarget = state.pressTarget;
905 dispatchPressEndEvents(event, context, props, state);
906
979 - if (pressTarget !== null && props.onPress) {
907 + if (pressTarget !== null) {
908 if (
909 !isKeyboardEvent &&
910 pressTarget !== null &&
@@ -1003,16 +931,17 @@ const PressResponder: ReactDOMEventResponder = {
931 if (
932 !(
933 wasLongPressed &&
1006 - props.onLongPressShouldCancelPress &&
1007 - props.onLongPressShouldCancelPress()
934 + props.enableLongPress &&
935 + props.longPressShouldCancelPress &&
936 + props.longPressShouldCancelPress()
937 )
938 ) {
939 dispatchEvent(
940 + 'onPress',
941 event,
942 context,
943 state,
944 'press',
1015 - props.onPress,
945 DiscreteEvent,
946 );
947 }
@@ -1077,8 +1006,11 @@ const PressResponder: ReactDOMEventResponder = {
1006 },
1007 };
1008
1080 -export const Press = React.unstable_createEvent(PressResponder);
1009 +export const PressResponder = React.unstable_createResponder(
1010 + 'Press',
1011 + pressResponderImpl,
1012 +);
1013
1082 -export function usePress(props: PressProps): void {
1083 - React.unstable_useEvent(Press, props);
1014 +export function usePressListener(props: PressListenerProps): void {
1015 + React.unstable_useListener(PressResponder, props);
1016 }
packages/react-events/src/dom/Scroll.js
+26 -26
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 PointerType,
@@ -18,13 +17,16 @@ import type {EventPriority} from 'shared/ReactTypes';
17
18 import React from 'react';
19
21 -type ScrollProps = {
22 - disabled: boolean,
20 +type ScrollListenerProps = {
21 onScroll: ScrollEvent => void,
22 onScrollDragStart: ScrollEvent => void,
23 onScrollDragEnd: ScrollEvent => void,
24 };
25
26 +type ScrollProps = {
27 + disabled: boolean,
28 +};
29 +
30 type ScrollState = {
31 direction: ScrollDirection,
32 pointerType: PointerType,
@@ -107,11 +109,11 @@ function createScrollEvent(
109 }
110
111 function dispatchEvent(
112 + eventPropName: string,
113 event: ?ReactDOMResponderEvent,
114 context: ReactDOMResponderContext,
115 state: ScrollState,
116 name: ScrollEventType,
114 - listener: (e: Object) => void,
117 eventPriority: EventPriority,
118 ): void {
119 const target = ((state.scrollTarget: any): Element | Document);
@@ -125,11 +127,10 @@ function dispatchEvent(
127 pointerType,
128 direction,
129 );
128 - context.dispatchEvent(syntheticEvent, listener, eventPriority);
130 + context.dispatchEvent(eventPropName, syntheticEvent, eventPriority);
131 }
132
131 -const ScrollResponder: ReactDOMEventResponder = {
132 - displayName: 'Scroll',
133 +const scrollResponderImpl = {
134 targetEventTypes,
135 getInitialState() {
136 return {
@@ -201,27 +202,23 @@ const ScrollResponder: ReactDOMEventResponder = {
202
203 if (state.isTouching && !state.isDragging) {
204 state.isDragging = true;
204 - if (props.onScrollDragStart) {
205 - dispatchEvent(
206 - event,
207 - context,
208 - state,
209 - 'scrolldragstart',
210 - props.onScrollDragStart,
211 - UserBlockingEvent,
212 - );
213 - }
214 - }
215 - if (props.onScroll) {
205 dispatchEvent(
206 + 'onScrollDragStart',
207 event,
208 context,
209 state,
220 - 'scroll',
221 - props.onScroll,
210 + 'scrolldragstart',
211 UserBlockingEvent,
212 );
213 }
214 + dispatchEvent(
215 + 'onScroll',
216 + event,
217 + context,
218 + state,
219 + 'scroll',
220 + UserBlockingEvent,
221 + );
222 break;
223 }
224 case 'keyup': {
@@ -256,13 +253,13 @@ const ScrollResponder: ReactDOMEventResponder = {
253 case 'touchcancel':
254 case 'touchend': {
255 if (state.isTouching) {
259 - if (state.isDragging && props.onScrollDragEnd) {
256 + if (state.isDragging) {
257 dispatchEvent(
258 + 'onScrollDragEnd',
259 event,
260 context,
261 state,
262 'scrolldragend',
265 - props.onScrollDragEnd,
263 UserBlockingEvent,
264 );
265 }
@@ -291,8 +288,11 @@ const ScrollResponder: ReactDOMEventResponder = {
288 },
289 };
290
294 -export const Scroll = React.unstable_createEvent(ScrollResponder);
291 +export const ScrollResponder = React.unstable_createResponder(
292 + 'Scroll',
293 + scrollResponderImpl,
294 +);
295
296 -export function useScroll(props: ScrollProps): void {
297 - React.unstable_useEvent(Scroll, props);
296 +export function useScrollListener(props: ScrollListenerProps): void {
297 + React.unstable_useListener(ScrollResponder, props);
298 }
packages/react-events/src/dom/Swipe.js
+34 -30
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - ReactDOMEventResponder,
11 ReactDOMResponderEvent,
12 ReactDOMResponderContext,
13 } from 'shared/ReactDOMTypes';
@@ -64,16 +63,16 @@ function createSwipeEvent(
63 }
64
65 function dispatchSwipeEvent(
66 + eventPropName: string,
67 context: ReactDOMResponderContext,
68 name: SwipeEventType,
69 - listener: SwipeEvent => void,
69 state: SwipeState,
70 eventPriority: EventPriority,
71 eventData?: EventData,
72 ) {
73 const target = ((state.swipeTarget: any): Element | Document);
74 const syntheticEvent = createSwipeEvent(context, name, target, eventData);
76 - context.dispatchEvent(syntheticEvent, listener, eventPriority);
75 + context.dispatchEvent(eventPropName, syntheticEvent, eventPriority);
76 }
77
78 type SwipeState = {
@@ -86,10 +85,10 @@ type SwipeState = {
85 swipeTarget: null | Element | Document,
86 x: number,
87 y: number,
88 + ownershipClaimed: boolean,
89 };
90
91 -const SwipeResponder: ReactDOMEventResponder = {
92 - displayName: 'Scroll',
91 +const swipeResponderImpl = {
92 targetEventTypes,
93 getInitialState(): SwipeState {
94 return {
@@ -102,6 +101,7 @@ const SwipeResponder: ReactDOMEventResponder = {
101 swipeTarget: null,
102 x: 0,
103 y: 0,
104 + ownershipClaimed: false,
105 };
106 },
107 onEvent(
@@ -127,8 +127,11 @@ const SwipeResponder: ReactDOMEventResponder = {
127
128 let shouldEnableSwiping = true;
129
130 - if (props.onShouldClaimOwnership && props.onShouldClaimOwnership()) {
130 + if (props.shouldClaimOwnership && props.shouldClaimOwnership()) {
131 shouldEnableSwiping = context.requestGlobalOwnership();
132 + if (shouldEnableSwiping) {
133 + state.ownershipClaimed = true;
134 + }
135 }
136 if (shouldEnableSwiping) {
137 state.isSwiping = true;
@@ -183,28 +186,26 @@ const SwipeResponder: ReactDOMEventResponder = {
186 }
187 const x = (obj: any).screenX;
188 const y = (obj: any).screenY;
186 - if (x < state.x && props.onSwipeLeft) {
189 + if (x < state.x) {
190 state.direction = 3;
188 - } else if (x > state.x && props.onSwipeRight) {
191 + } else if (x > state.x) {
192 state.direction = 1;
193 }
194 state.x = x;
195 state.y = y;
193 - if (props.onSwipeMove) {
194 - const eventData = {
195 - diffX: x - state.startX,
196 - diffY: y - state.startY,
197 - };
198 - dispatchSwipeEvent(
199 - context,
200 - 'swipemove',
201 - props.onSwipeMove,
202 - state,
203 - UserBlockingEvent,
204 - eventData,
205 - );
206 - (nativeEvent: any).preventDefault();
207 - }
196 + const eventData = {
197 + diffX: x - state.startX,
198 + diffY: y - state.startY,
199 + };
200 + dispatchSwipeEvent(
201 + 'onSwipeMove',
202 + context,
203 + 'swipemove',
204 + state,
205 + UserBlockingEvent,
206 + eventData,
207 + );
208 + (nativeEvent: any).preventDefault();
209 }
210 break;
211 }
@@ -217,7 +218,7 @@ const SwipeResponder: ReactDOMEventResponder = {
218 if (state.x === state.startX && state.y === state.startY) {
219 return;
220 }
220 - if (props.onShouldClaimOwnership) {
221 + if (state.ownershipClaimed) {
222 context.releaseOwnership();
223 }
224 const direction = state.direction;
@@ -225,17 +226,17 @@ const SwipeResponder: ReactDOMEventResponder = {
226 if (direction !== lastDirection) {
227 if (props.onSwipeLeft && direction === 3) {
228 dispatchSwipeEvent(
229 + 'onSwipeLeft',
230 context,
231 'swipeleft',
230 - props.onSwipeLeft,
232 state,
233 DiscreteEvent,
234 );
235 } else if (props.onSwipeRight && direction === 1) {
236 dispatchSwipeEvent(
237 + 'onSwipeRight',
238 context,
239 'swiperight',
238 - props.onSwipeRight,
240 state,
241 DiscreteEvent,
242 );
@@ -243,9 +244,9 @@ const SwipeResponder: ReactDOMEventResponder = {
244 }
245 if (props.onSwipeEnd) {
246 dispatchSwipeEvent(
247 + 'onSwipeEnd',
248 context,
249 'swipeend',
248 - props.onSwipeEnd,
250 state,
251 DiscreteEvent,
252 );
@@ -262,8 +263,11 @@ const SwipeResponder: ReactDOMEventResponder = {
263 },
264 };
265
265 -export const Swipe = React.unstable_createEvent(SwipeResponder);
266 +export const SwipeResponder = React.unstable_createResponder(
267 + 'Swipe',
268 + swipeResponderImpl,
269 +);
270
267 -export function useSwipe(props: Object): void {
268 - React.unstable_useEvent(Swipe, props);
271 +export function useSwipeListener(props: Object): void {
272 + React.unstable_useListener(SwipeResponder, props);
273 }
packages/react-events/src/dom/__tests__/Drag-test.internal.js
+80 -11
@@ -12,7 +12,9 @@
12 let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15 -let Drag;
15 +let DragResponder;
16 +let useDragListener;
17 +
18 describe('Drag event responder', () => {
19 let container;
20
@@ -22,7 +24,8 @@ describe('Drag event responder', () => {
24 ReactFeatureFlags.enableFlareAPI = true;
25 React = require('react');
26 ReactDOM = require('react-dom');
25 - Drag = require('react-events/drag').Drag;
27 + DragResponder = require('react-events/drag').DragResponder;
28 + useDragListener = require('react-events/drag').useDragListener;
29
30 container = document.createElement('div');
31 document.body.appendChild(container);
@@ -43,10 +46,13 @@ describe('Drag event responder', () => {
46 }
47
48 function Component() {
49 + useDragListener({
50 + onDragChange: handleOnDrag,
51 + });
52 return (
47 - <Drag onDragChange={handleOnDrag}>
48 - <div ref={divRef}>Drag me!</div>
49 - </Drag>
53 + <div ref={divRef} responders={<DragResponder />}>
54 + Drag me!
55 + </div>
56 );
57 }
58
@@ -96,10 +102,70 @@ describe('Drag event responder', () => {
102 }
103
104 function Component() {
105 + useDragListener({
106 + onDragStart: handleDragStart,
107 + onDragEnd: handleDragEnd,
108 + });
109 return (
100 - <Drag onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
101 - <div ref={divRef}>Drag me!</div>
102 - </Drag>
110 + <div ref={divRef} responders={<DragResponder />}>
111 + Drag me!
112 + </div>
113 + );
114 + }
115 +
116 + ReactDOM.render(<Component />, container);
117 +
118 + const mouseOverEvent = document.createEvent('MouseEvents');
119 + mouseOverEvent.initEvent('mousedown', true, true);
120 + divRef.current.dispatchEvent(mouseOverEvent);
121 +
122 + const mouseMoveEvent = document.createEvent('MouseEvents');
123 + for (let index = 0; index <= 20; index++) {
124 + mouseMoveEvent.initMouseEvent(
125 + 'mousemove',
126 + true,
127 + true,
128 + window,
129 + 1,
130 + index,
131 + index,
132 + 50,
133 + 50,
134 + );
135 + divRef.current.dispatchEvent(mouseMoveEvent);
136 + }
137 + divRef.current.dispatchEvent(mouseMoveEvent);
138 +
139 + const mouseUpEvent = document.createEvent('MouseEvents');
140 + mouseUpEvent.initEvent('mouseup', true, true);
141 + divRef.current.dispatchEvent(mouseUpEvent);
142 +
143 + expect(events).toEqual(['dragstart', 'dragend']);
144 + });
145 +
146 + it('should support onDragStart and onDragEnd with ownership', () => {
147 + let divRef = React.createRef();
148 + let events = [];
149 +
150 + function handleDragStart() {
151 + events.push('dragstart');
152 + }
153 +
154 + function handleDragEnd() {
155 + events.push('dragend');
156 + }
157 +
158 + function Component() {
159 + useDragListener({
160 + onDragStart: handleDragStart,
161 + onDragEnd: handleDragEnd,
162 + });
163 + return (
164 + <div
165 + ref={divRef}
166 + responders={<DragResponder shouldClaimOwnership={() => true} />}>
167 + Drag me!
168 + </div>
169 );
170 }
171
@@ -145,10 +211,13 @@ describe('Drag event responder', () => {
211 }
212
213 function Component() {
214 + useDragListener({
215 + onDragMove: handleDragMove,
216 + });
217 return (
149 - <Drag onDragMove={handleDragMove}>
150 - <div ref={divRef}>Drag me!</div>
151 - </Drag>
218 + <div ref={divRef} responders={<DragResponder />}>
219 + Drag me!
220 + </div>
221 );
222 }
223
packages/react-events/src/dom/__tests__/Focus-test.internal.js
+106 -72
@@ -12,8 +12,10 @@
12 let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15 -let Focus;
16 -let FocusWithin;
15 +let FocusResponder;
16 +let FocusWithinResponder;
17 +let useFocusListener;
18 +let useFocusWithinListener;
19
20 const createEvent = (type, data) => {
21 const event = document.createEvent('CustomEvent');
@@ -39,8 +41,10 @@ const modulesInit = () => {
41 ReactFeatureFlags.enableFlareAPI = true;
42 React = require('react');
43 ReactDOM = require('react-dom');
42 - Focus = require('react-events/focus').Focus;
43 - FocusWithin = require('react-events/focus').FocusWithin;
44 + FocusResponder = require('react-events/focus').FocusResponder;
45 + FocusWithinResponder = require('react-events/focus').FocusWithinResponder;
46 + useFocusListener = require('react-events/focus').useFocusListener;
47 + useFocusWithinListener = require('react-events/focus').useFocusWithinListener;
48 };
49
50 describe('Focus event responder', () => {
@@ -67,12 +71,16 @@ describe('Focus event responder', () => {
71 onBlur = jest.fn();
72 onFocus = jest.fn();
73 ref = React.createRef();
70 - const element = (
71 - <Focus disabled={true} onBlur={onBlur} onFocus={onFocus}>
72 - <div ref={ref} />
73 - </Focus>
74 - );
75 - ReactDOM.render(element, container);
74 + const Component = () => {
75 + useFocusListener({
76 + onBlur,
77 + onFocus,
78 + });
79 + return (
80 + <div ref={ref} responders={<FocusResponder disabled={true} />} />
81 + );
82 + };
83 + ReactDOM.render(<Component />, container);
84 });
85
86 it('prevents custom events being dispatched', () => {
@@ -89,12 +97,13 @@ describe('Focus event responder', () => {
97 beforeEach(() => {
98 onBlur = jest.fn();
99 ref = React.createRef();
92 - const element = (
93 - <Focus onBlur={onBlur}>
94 - <div ref={ref} />
95 - </Focus>
96 - );
97 - ReactDOM.render(element, container);
100 + const Component = () => {
101 + useFocusListener({
102 + onBlur,
103 + });
104 + return <div ref={ref} responders={<FocusResponder />} />;
105 + };
106 + ReactDOM.render(<Component />, container);
107 });
108
109 it('is called after "blur" event', () => {
@@ -111,14 +120,17 @@ describe('Focus event responder', () => {
120 onFocus = jest.fn();
121 ref = React.createRef();
122 innerRef = React.createRef();
114 - const element = (
115 - <Focus onFocus={onFocus}>
116 - <div ref={ref}>
123 + const Component = () => {
124 + useFocusListener({
125 + onFocus,
126 + });
127 + return (
128 + <div ref={ref} responders={<FocusResponder />}>
129 <a ref={innerRef} />
130 </div>
119 - </Focus>
120 - );
121 - ReactDOM.render(element, container);
131 + );
132 + };
133 + ReactDOM.render(<Component />, container);
134 };
135
136 beforeEach(componentInit);
@@ -239,14 +251,17 @@ describe('Focus event responder', () => {
251 onFocusChange = jest.fn();
252 ref = React.createRef();
253 innerRef = React.createRef();
242 - const element = (
243 - <Focus onFocusChange={onFocusChange}>
244 - <div ref={ref}>
254 + const Component = () => {
255 + useFocusListener({
256 + onFocusChange,
257 + });
258 + return (
259 + <div ref={ref} responders={<FocusResponder />}>
260 <div ref={innerRef} />
261 </div>
247 - </Focus>
248 - );
249 - ReactDOM.render(element, container);
262 + );
263 + };
264 + ReactDOM.render(<Component />, container);
265 });
266
267 it('is called after "blur" and "focus" events', () => {
@@ -273,14 +288,17 @@ describe('Focus event responder', () => {
288 onFocusVisibleChange = jest.fn();
289 ref = React.createRef();
290 innerRef = React.createRef();
276 - const element = (
277 - <Focus onFocusVisibleChange={onFocusVisibleChange}>
278 - <div ref={ref}>
291 + const Component = () => {
292 + useFocusListener({
293 + onFocusVisibleChange,
294 + });
295 + return (
296 + <div ref={ref} responders={<FocusResponder />}>
297 <div ref={innerRef} />
298 </div>
281 - </Focus>
282 - );
283 - ReactDOM.render(element, container);
299 + );
300 + };
301 + ReactDOM.render(<Component />, container);
302 });
303
304 it('is called after "focus" and "blur" if keyboard navigation is active', () => {
@@ -343,23 +361,29 @@ describe('Focus event responder', () => {
361 events.push(msg);
362 };
363
346 - const element = (
347 - <Focus
348 - onBlur={createEventHandler('outer: onBlur')}
349 - onFocus={createEventHandler('outer: onFocus')}
350 - onFocusChange={createEventHandler('outer: onFocusChange')}>
351 - <div ref={outerRef}>
352 - <Focus
353 - onBlur={createEventHandler('inner: onBlur')}
354 - onFocus={createEventHandler('inner: onFocus')}
355 - onFocusChange={createEventHandler('inner: onFocusChange')}>
356 - <div ref={innerRef} />
357 - </Focus>
364 + const Inner = () => {
365 + useFocusListener({
366 + onBlur: createEventHandler('inner: onBlur'),
367 + onFocus: createEventHandler('inner: onFocus'),
368 + onFocusChange: createEventHandler('inner: onFocusChange'),
369 + });
370 + return <div ref={innerRef} responders={<FocusResponder />} />;
371 + };
372 +
373 + const Outer = () => {
374 + useFocusListener({
375 + onBlur: createEventHandler('outer: onBlur'),
376 + onFocus: createEventHandler('outer: onFocus'),
377 + onFocusChange: createEventHandler('outer: onFocusChange'),
378 + });
379 + return (
380 + <div ref={outerRef} responders={<FocusResponder />}>
381 + <Inner />
382 </div>
359 - </Focus>
360 - );
383 + );
384 + };
385
362 - ReactDOM.render(element, container);
386 + ReactDOM.render(<Outer />, container);
387
388 outerRef.current.dispatchEvent(createEvent('focus'));
389 outerRef.current.dispatchEvent(createEvent('blur'));
@@ -379,7 +403,7 @@ describe('Focus event responder', () => {
403 });
404
405 it('expect displayName to show up for event component', () => {
382 - expect(Focus.responder.displayName).toBe('Focus');
406 + expect(FocusResponder.displayName).toBe('Focus');
407 });
408 });
409
@@ -407,15 +431,19 @@ describe('FocusWithin event responder', () => {
431 onFocusWithinChange = jest.fn();
432 onFocusWithinVisibleChange = jest.fn();
433 ref = React.createRef();
410 - const element = (
411 - <FocusWithin
412 - disabled={true}
413 - onFocusWithinChange={onFocusWithinChange}
414 - onFocusWithinVisibleChange={onFocusWithinVisibleChange}>
415 - <div ref={ref} />
416 - </FocusWithin>
417 - );
418 - ReactDOM.render(element, container);
434 + const Component = () => {
435 + useFocusWithinListener({
436 + onFocusWithinChange,
437 + onFocusWithinVisibleChange,
438 + });
439 + return (
440 + <div
441 + ref={ref}
442 + responders={<FocusWithinResponder disabled={true} />}
443 + />
444 + );
445 + };
446 + ReactDOM.render(<Component />, container);
447 });
448
449 it('prevents custom events being dispatched', () => {
@@ -434,15 +462,18 @@ describe('FocusWithin event responder', () => {
462 ref = React.createRef();
463 innerRef = React.createRef();
464 innerRef2 = React.createRef();
437 - const element = (
438 - <FocusWithin onFocusWithinChange={onFocusWithinChange}>
439 - <div ref={ref}>
465 + const Component = () => {
466 + useFocusWithinListener({
467 + onFocusWithinChange,
468 + });
469 + return (
470 + <div ref={ref} responders={<FocusWithinResponder />}>
471 <div ref={innerRef} />
472 <div ref={innerRef2} />
473 </div>
443 - </FocusWithin>
444 - );
445 - ReactDOM.render(element, container);
474 + );
475 + };
476 + ReactDOM.render(<Component />, container);
477 });
478
479 it('is called after "blur" and "focus" events on focus target', () => {
@@ -502,15 +533,18 @@ describe('FocusWithin event responder', () => {
533 ref = React.createRef();
534 innerRef = React.createRef();
535 innerRef2 = React.createRef();
505 - const element = (
506 - <FocusWithin onFocusWithinVisibleChange={onFocusWithinVisibleChange}>
507 - <div ref={ref}>
536 + const Component = () => {
537 + useFocusWithinListener({
538 + onFocusWithinVisibleChange,
539 + });
540 + return (
541 + <div ref={ref} responders={<FocusWithinResponder />}>
542 <div ref={innerRef} />
543 <div ref={innerRef2} />
544 </div>
511 - </FocusWithin>
512 - );
513 - ReactDOM.render(element, container);
545 + );
546 + };
547 + ReactDOM.render(<Component />, container);
548 });
549
550 it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
@@ -616,6 +650,6 @@ describe('FocusWithin event responder', () => {
650 });
651
652 it('expect displayName to show up for event component', () => {
619 - expect(FocusWithin.responder.displayName).toBe('FocusWithin');
653 + expect(FocusWithinResponder.displayName).toBe('FocusWithin');
654 });
655 });
packages/react-events/src/dom/__tests__/FocusScope-test.internal.js
+39 -49
@@ -12,7 +12,7 @@
12 let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15 -let FocusScope;
15 +let FocusScopeResponder;
16
17 const createTabForward = type => {
18 const event = new KeyboardEvent('keydown', {
@@ -42,8 +42,8 @@ describe('FocusScope event responder', () => {
42 ReactFeatureFlags.enableFlareAPI = true;
43 React = require('react');
44 ReactDOM = require('react-dom');
45 - FocusScope = require('react-events/focus-scope');
46 -
45 + FocusScopeResponder = require('react-events/focus-scope')
46 + .FocusScopeResponder;
47 container = document.createElement('div');
48 document.body.appendChild(container);
49 });
@@ -62,14 +62,12 @@ describe('FocusScope event responder', () => {
62 const divRef = React.createRef();
63
64 const SimpleFocusScope = () => (
65 - <div>
66 - <FocusScope autoFocus={true}>
67 - <input ref={inputRef} />
68 - <button ref={buttonRef} />
69 - <div ref={divRef} tabIndex={0} />
70 - <input ref={input2Ref} tabIndex={-1} />
71 - <button ref={butto2nRef} />
72 - </FocusScope>
65 + <div responders={<FocusScopeResponder autoFocus={true} />}>
66 + <input ref={inputRef} />
67 + <button ref={buttonRef} />
68 + <div ref={divRef} tabIndex={0} />
69 + <input ref={input2Ref} tabIndex={-1} />
70 + <button ref={butto2nRef} />
71 </div>
72 );
73
@@ -92,13 +90,11 @@ describe('FocusScope event responder', () => {
90 const button2Ref = React.createRef();
91
92 const SimpleFocusScope = () => (
95 - <div>
96 - <FocusScope autoFocus={true} contain={true}>
97 - <input ref={inputRef} tabIndex={-1} />
98 - <button ref={buttonRef} id={1} />
99 - <button ref={button2Ref} id={2} />
100 - <input ref={input2Ref} tabIndex={-1} />
101 - </FocusScope>
93 + <div responders={<FocusScopeResponder autoFocus={true} contain={true} />}>
94 + <input ref={inputRef} tabIndex={-1} />
95 + <button ref={buttonRef} id={1} />
96 + <button ref={button2Ref} id={2} />
97 + <input ref={input2Ref} tabIndex={-1} />
98 </div>
99 );
100
@@ -125,17 +121,15 @@ describe('FocusScope event responder', () => {
121 const button4Ref = React.createRef();
122
123 const SimpleFocusScope = () => (
128 - <div>
129 - <FocusScope>
130 - <input ref={inputRef} tabIndex={-1} />
131 - <button ref={buttonRef} id={1} />
132 - <FocusScope>
133 - <button ref={button2Ref} id={2} />
134 - <button ref={button3Ref} id={3} />
135 - </FocusScope>
136 - <input ref={input2Ref} tabIndex={-1} />
137 - <button ref={button4Ref} id={4} />
138 - </FocusScope>
124 + <div responders={<FocusScopeResponder />}>
125 + <input ref={inputRef} tabIndex={-1} />
126 + <button ref={buttonRef} id={1} />
127 + <div responders={<FocusScopeResponder />}>
128 + <button ref={button2Ref} id={2} />
129 + <button ref={button3Ref} id={3} />
130 + </div>
131 + <input ref={input2Ref} tabIndex={-1} />
132 + <button ref={button4Ref} id={4} />
133 </div>
134 );
135
@@ -163,17 +157,15 @@ describe('FocusScope event responder', () => {
157 const button4Ref = React.createRef();
158
159 const SimpleFocusScope = () => (
166 - <div>
167 - <FocusScope>
168 - <input ref={inputRef} tabIndex={-1} />
169 - <button ref={buttonRef} id={1} />
170 - <FocusScope contain={true}>
171 - <button ref={button2Ref} id={2} />
172 - <button ref={button3Ref} id={3} />
173 - </FocusScope>
174 - <input ref={input2Ref} tabIndex={-1} />
175 - <button ref={button4Ref} id={4} />
176 - </FocusScope>
160 + <div responders={<FocusScopeResponder />}>
161 + <input ref={inputRef} tabIndex={-1} />
162 + <button ref={buttonRef} id={1} />
163 + <div responders={<FocusScopeResponder contain={true} />}>
164 + <button ref={button2Ref} id={2} />
165 + <button ref={button3Ref} id={3} />
166 + </div>
167 + <input ref={input2Ref} tabIndex={-1} />
168 + <button ref={button4Ref} id={4} />
169 </div>
170 );
171
@@ -215,15 +207,13 @@ describe('FocusScope event responder', () => {
207 }
208
209 const SimpleFocusScope = () => (
218 - <div>
219 - <FocusScope>
220 - <button ref={buttonRef} id={1} />
221 - <button ref={button2Ref} id={2} />
222 - <React.Suspense fallback={<button ref={button3Ref} id={3} />}>
223 - <Component />
224 - </React.Suspense>
225 - <button ref={button4Ref} id={4} />
226 - </FocusScope>
210 + <div responders={<FocusScopeResponder />}>
211 + <button ref={buttonRef} id={1} />
212 + <button ref={button2Ref} id={2} />
213 + <React.Suspense fallback={<button ref={button3Ref} id={3} />}>
214 + <Component />
215 + </React.Suspense>
216 + <button ref={button4Ref} id={4} />
217 </div>
218 );
219
packages/react-events/src/dom/__tests__/Hover-test.internal.js
+191 -135
@@ -14,7 +14,8 @@ let ReactFeatureFlags;
14 let ReactDOM;
15 let TestUtils;
16 let Scheduler;
17 -let Hover;
17 +let HoverResponder;
18 +let useHoverListener;
19
20 const createEvent = (type, data) => {
21 const event = document.createEvent('CustomEvent');
@@ -45,10 +46,13 @@ describe('Hover event responder', () => {
46 jest.resetModules();
47 ReactFeatureFlags = require('shared/ReactFeatureFlags');
48 ReactFeatureFlags.enableFlareAPI = true;
49 + ReactFeatureFlags.enableUserBlockingEvents = true;
50 React = require('react');
51 ReactDOM = require('react-dom');
50 - Hover = require('react-events/hover').Hover;
51 -
52 + TestUtils = require('react-dom/test-utils');
53 + Scheduler = require('scheduler');
54 + HoverResponder = require('react-events/hover').HoverResponder;
55 + useHoverListener = require('react-events/hover').useHoverListener;
56 container = document.createElement('div');
57 document.body.appendChild(container);
58 });
@@ -66,15 +70,16 @@ describe('Hover event responder', () => {
70 onHoverStart = jest.fn();
71 onHoverEnd = jest.fn();
72 ref = React.createRef();
69 - const element = (
70 - <Hover
71 - disabled={true}
72 - onHoverStart={onHoverStart}
73 - onHoverEnd={onHoverEnd}>
74 - <div ref={ref} />
75 - </Hover>
76 - );
77 - ReactDOM.render(element, container);
73 + const Component = () => {
74 + useHoverListener({
75 + onHoverStart: onHoverStart,
76 + onHoverEnd: onHoverEnd,
77 + });
78 + return (
79 + <div ref={ref} responders={<HoverResponder disabled={true} />} />
80 + );
81 + };
82 + ReactDOM.render(<Component />, container);
83 });
84
85 it('prevents custom events being dispatched', () => {
@@ -91,12 +96,13 @@ describe('Hover event responder', () => {
96 beforeEach(() => {
97 onHoverStart = jest.fn();
98 ref = React.createRef();
94 - const element = (
95 - <Hover onHoverStart={onHoverStart}>
96 - <div ref={ref} />
97 - </Hover>
98 - );
99 - ReactDOM.render(element, container);
99 + const Component = () => {
100 + useHoverListener({
101 + onHoverStart: onHoverStart,
102 + });
103 + return <div ref={ref} responders={<HoverResponder />} />;
104 + };
105 + ReactDOM.render(<Component />, container);
106 });
107
108 it('is called after "pointerover" event', () => {
@@ -164,12 +170,18 @@ describe('Hover event responder', () => {
170
171 describe('delayHoverStart', () => {
172 it('can be configured', () => {
167 - const element = (
168 - <Hover delayHoverStart={2000} onHoverStart={onHoverStart}>
169 - <div ref={ref} />
170 - </Hover>
171 - );
172 - ReactDOM.render(element, container);
173 + const Component = () => {
174 + useHoverListener({
175 + onHoverStart: onHoverStart,
176 + });
177 + return (
178 + <div
179 + ref={ref}
180 + responders={<HoverResponder delayHoverStart={2000} />}
181 + />
182 + );
183 + };
184 + ReactDOM.render(<Component />, container);
185
186 ref.current.dispatchEvent(createEvent('pointerover'));
187 jest.advanceTimersByTime(1999);
@@ -179,12 +191,18 @@ describe('Hover event responder', () => {
191 });
192
193 it('is reset if "pointerout" is dispatched during a delay', () => {
182 - const element = (
183 - <Hover delayHoverStart={500} onHoverStart={onHoverStart}>
184 - <div ref={ref} />
185 - </Hover>
186 - );
187 - ReactDOM.render(element, container);
194 + const Component = () => {
195 + useHoverListener({
196 + onHoverStart: onHoverStart,
197 + });
198 + return (
199 + <div
200 + ref={ref}
201 + responders={<HoverResponder delayHoverStart={500} />}
202 + />
203 + );
204 + };
205 + ReactDOM.render(<Component />, container);
206
207 ref.current.dispatchEvent(createEvent('pointerover'));
208 jest.advanceTimersByTime(499);
@@ -197,27 +215,38 @@ describe('Hover event responder', () => {
215 });
216
217 it('onHoverStart is called synchronously if delay is 0ms', () => {
200 - const element = (
201 - <Hover delayHoverStart={0} onHoverStart={onHoverStart}>
202 - <div ref={ref} />
203 - </Hover>
204 - );
205 - ReactDOM.render(element, container);
218 + const Component = () => {
219 + useHoverListener({
220 + onHoverStart: onHoverStart,
221 + });
222 + return (
223 + <div
224 + ref={ref}
225 + responders={<HoverResponder delayHoverStart={0} />}
226 + />
227 + );
228 + };
229 + ReactDOM.render(<Component />, container);
230
231 ref.current.dispatchEvent(createEvent('pointerover'));
232 expect(onHoverStart).toHaveBeenCalledTimes(1);
233 });
234
235 it('onHoverStart is only called once per active hover', () => {
212 - const element = (
213 - <Hover
214 - delayHoverStart={500}
215 - delayHoverEnd={100}
216 - onHoverStart={onHoverStart}>
217 - <div ref={ref} />
218 - </Hover>
219 - );
220 - ReactDOM.render(element, container);
236 + const Component = () => {
237 + useHoverListener({
238 + onHoverStart: onHoverStart,
239 + });
240 + return (
241 + <div
242 + ref={ref}
243 + responders={
244 + <HoverResponder delayHoverStart={500} delayHoverEnd={100} />
245 + }
246 + />
247 + );
248 + };
249 + ReactDOM.render(<Component />, container);
250
251 ref.current.dispatchEvent(createEvent('pointerover'));
252 jest.advanceTimersByTime(500);
@@ -237,12 +266,13 @@ describe('Hover event responder', () => {
266 beforeEach(() => {
267 onHoverChange = jest.fn();
268 ref = React.createRef();
240 - const element = (
241 - <Hover onHoverChange={onHoverChange}>
242 - <div ref={ref} />
243 - </Hover>
244 - );
245 - ReactDOM.render(element, container);
269 + const Component = () => {
270 + useHoverListener({
271 + onHoverChange,
272 + });
273 + return <div ref={ref} responders={<HoverResponder />} />;
274 + };
275 + ReactDOM.render(<Component />, container);
276 });
277
278 it('is called after "pointerover" and "pointerout" events', () => {
@@ -265,16 +295,6 @@ describe('Hover event responder', () => {
295 });
296
297 it('should be user-blocking but not discrete', async () => {
268 - // This is currently behind a feature flag
269 - jest.resetModules();
270 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
271 - ReactFeatureFlags.enableFlareAPI = true;
272 - ReactFeatureFlags.enableUserBlockingEvents = true;
273 - React = require('react');
274 - ReactDOM = require('react-dom');
275 - TestUtils = require('react-dom/test-utils');
276 - Scheduler = require('scheduler');
277 -
298 const {act} = TestUtils;
299 const {useState} = React;
300
@@ -285,10 +305,13 @@ describe('Hover event responder', () => {
305 const target = React.createRef(null);
306 function Foo() {
307 const [isHover, setHover] = useState(false);
308 + useHoverListener({
309 + onHoverChange: setHover,
310 + });
311 return (
289 - <Hover onHoverChange={setHover}>
290 - <div ref={target}>{isHover ? 'hovered' : 'not hovered'}</div>
291 - </Hover>
312 + <div ref={target} responders={<HoverResponder />}>
313 + {isHover ? 'hovered' : 'not hovered'}
314 + </div>
315 );
316 }
317
@@ -313,12 +336,13 @@ describe('Hover event responder', () => {
336 beforeEach(() => {
337 onHoverEnd = jest.fn();
338 ref = React.createRef();
316 - const element = (
317 - <Hover onHoverEnd={onHoverEnd}>
318 - <div ref={ref} />
319 - </Hover>
320 - );
321 - ReactDOM.render(element, container);
339 + const Component = () => {
340 + useHoverListener({
341 + onHoverEnd,
342 + });
343 + return <div ref={ref} responders={<HoverResponder />} />;
344 + };
345 + ReactDOM.render(<Component />, container);
346 });
347
348 it('is called after "pointerout" event', () => {
@@ -378,12 +402,18 @@ describe('Hover event responder', () => {
402
403 describe('delayHoverEnd', () => {
404 it('can be configured', () => {
381 - const element = (
382 - <Hover delayHoverEnd={2000} onHoverEnd={onHoverEnd}>
383 - <div ref={ref} />
384 - </Hover>
385 - );
386 - ReactDOM.render(element, container);
405 + const Component = () => {
406 + useHoverListener({
407 + onHoverEnd,
408 + });
409 + return (
410 + <div
411 + ref={ref}
412 + responders={<HoverResponder delayHoverEnd={2000} />}
413 + />
414 + );
415 + };
416 + ReactDOM.render(<Component />, container);
417
418 ref.current.dispatchEvent(createEvent('pointerover'));
419 ref.current.dispatchEvent(createEvent('pointerout'));
@@ -394,12 +424,15 @@ describe('Hover event responder', () => {
424 });
425
426 it('delayHoverEnd is called synchronously if delay is 0ms', () => {
397 - const element = (
398 - <Hover delayHoverEnd={0} onHoverEnd={onHoverEnd}>
399 - <div ref={ref} />
400 - </Hover>
401 - );
402 - ReactDOM.render(element, container);
427 + const Component = () => {
428 + useHoverListener({
429 + onHoverEnd,
430 + });
431 + return (
432 + <div ref={ref} responders={<HoverResponder delayHoverEnd={0} />} />
433 + );
434 + };
435 + ReactDOM.render(<Component />, container);
436
437 ref.current.dispatchEvent(createEvent('pointerover'));
438 ref.current.dispatchEvent(createEvent('pointerout'));
@@ -407,12 +440,18 @@ describe('Hover event responder', () => {
440 });
441
442 it('onHoverEnd is only called once per active hover', () => {
410 - const element = (
411 - <Hover delayHoverEnd={500} onHoverEnd={onHoverEnd}>
412 - <div ref={ref} />
413 - </Hover>
414 - );
415 - ReactDOM.render(element, container);
443 + const Component = () => {
444 + useHoverListener({
445 + onHoverEnd,
446 + });
447 + return (
448 + <div
449 + ref={ref}
450 + responders={<HoverResponder delayHoverEnd={500} />}
451 + />
452 + );
453 + };
454 + ReactDOM.render(<Component />, container);
455
456 ref.current.dispatchEvent(createEvent('pointerover'));
457 ref.current.dispatchEvent(createEvent('pointerout'));
@@ -425,12 +464,18 @@ describe('Hover event responder', () => {
464 });
465
466 it('onHoverEnd is not called if "pointerover" is dispatched during a delay', () => {
428 - const element = (
429 - <Hover delayHoverEnd={500} onHoverEnd={onHoverEnd}>
430 - <div ref={ref} />
431 - </Hover>
432 - );
433 - ReactDOM.render(element, container);
467 + const Component = () => {
468 + useHoverListener({
469 + onHoverEnd,
470 + });
471 + return (
472 + <div
473 + ref={ref}
474 + responders={<HoverResponder delayHoverEnd={500} />}
475 + />
476 + );
477 + };
478 + ReactDOM.render(<Component />, container);
479
480 ref.current.dispatchEvent(createEvent('pointerover'));
481 ref.current.dispatchEvent(createEvent('pointerout'));
@@ -441,15 +486,20 @@ describe('Hover event responder', () => {
486 });
487
488 it('onHoverEnd is not called if there was no active hover', () => {
444 - const element = (
445 - <Hover
446 - delayHoverStart={500}
447 - delayHoverEnd={100}
448 - onHoverEnd={onHoverEnd}>
449 - <div ref={ref} />
450 - </Hover>
451 - );
452 - ReactDOM.render(element, container);
489 + const Component = () => {
490 + useHoverListener({
491 + onHoverEnd,
492 + });
493 + return (
494 + <div
495 + ref={ref}
496 + responders={
497 + <HoverResponder delayHoverStart={500} delayHoverEnd={100} />
498 + }
499 + />
500 + );
501 + };
502 + ReactDOM.render(<Component />, container);
503
504 ref.current.dispatchEvent(createEvent('pointerover'));
505 ref.current.dispatchEvent(createEvent('pointerout'));
@@ -463,12 +513,13 @@ describe('Hover event responder', () => {
513 it('is called after "pointermove"', () => {
514 const onHoverMove = jest.fn();
515 const ref = React.createRef();
466 - const element = (
467 - <Hover onHoverMove={onHoverMove}>
468 - <div ref={ref} />
469 - </Hover>
470 - );
471 - ReactDOM.render(element, container);
516 + const Component = () => {
517 + useHoverListener({
518 + onHoverMove,
519 + });
520 + return <div ref={ref} responders={<HoverResponder />} />;
521 + };
522 + ReactDOM.render(<Component />, container);
523
524 ref.current.getBoundingClientRect = () => ({
525 top: 50,
@@ -498,23 +549,28 @@ describe('Hover event responder', () => {
549 events.push(msg);
550 };
551
501 - const element = (
502 - <Hover
503 - onHoverStart={createEventHandler('outer: onHoverStart')}
504 - onHoverEnd={createEventHandler('outer: onHoverEnd')}
505 - onHoverChange={createEventHandler('outer: onHoverChange')}>
506 - <div ref={outerRef}>
507 - <Hover
508 - onHoverStart={createEventHandler('inner: onHoverStart')}
509 - onHoverEnd={createEventHandler('inner: onHoverEnd')}
510 - onHoverChange={createEventHandler('inner: onHoverChange')}>
511 - <div ref={innerRef} />
512 - </Hover>
513 - </div>
514 - </Hover>
515 - );
552 + const Inner = () => {
553 + useHoverListener({
554 + onHoverStart: createEventHandler('inner: onHoverStart'),
555 + onHoverEnd: createEventHandler('inner: onHoverEnd'),
556 + onHoverChange: createEventHandler('inner: onHoverChange'),
557 + });
558 + return <div ref={innerRef} responders={<HoverResponder />} />;
559 + };
560
517 - ReactDOM.render(element, container);
561 + const Outer = () => {
562 + useHoverListener({
563 + onHoverStart: createEventHandler('outer: onHoverStart'),
564 + onHoverEnd: createEventHandler('outer: onHoverEnd'),
565 + onHoverChange: createEventHandler('outer: onHoverChange'),
566 + });
567 + return (
568 + <div ref={outerRef} responders={<HoverResponder />}>
569 + <Inner />
570 + </div>
571 + );
572 + };
573 + ReactDOM.render(<Outer />, container);
574
575 outerRef.current.dispatchEvent(createEvent('pointerover'));
576 outerRef.current.dispatchEvent(
@@ -546,7 +602,7 @@ describe('Hover event responder', () => {
602 });
603
604 it('expect displayName to show up for event component', () => {
549 - expect(Hover.responder.displayName).toBe('Hover');
605 + expect(HoverResponder.displayName).toBe('Hover');
606 });
607
608 it('should correctly pass through event properties', () => {
@@ -569,15 +625,15 @@ describe('Hover event responder', () => {
625 timeStamps.push(event.timeStamp);
626 eventLog.push(propertiesWeCareAbout);
627 };
572 - const element = (
573 - <Hover
574 - onHoverStart={logEvent}
575 - onHoverEnd={logEvent}
576 - onHoverMove={logEvent}>
577 - <button ref={ref} />
578 - </Hover>
579 - );
580 - ReactDOM.render(element, container);
628 + const Component = () => {
629 + useHoverListener({
630 + onHoverStart: logEvent,
631 + onHoverEnd: logEvent,
632 + onHoverMove: logEvent,
633 + });
634 + return <div ref={ref} responders={<HoverResponder />} />;
635 + };
636 + ReactDOM.render(<Component />, container);
637
638 ref.current.getBoundingClientRect = () => ({
639 top: 10,
packages/react-events/src/dom/__tests__/Input-test.internal.js
+303 -139
@@ -12,8 +12,10 @@
12 let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15 -let Input;
16 -let Press;
15 +let InputResponder;
16 +let useInputListener;
17 +let PressResponder;
18 +let usePressListener;
19 let Scheduler;
20
21 const setUntrackedChecked = Object.getOwnPropertyDescriptor(
@@ -39,8 +41,10 @@ const modulesInit = () => {
41 React = require('react');
42 ReactDOM = require('react-dom');
43 Scheduler = require('scheduler');
42 - Input = require('react-events/input').Input;
43 - Press = require('react-events/press').Press;
44 + InputResponder = require('react-events/input').InputResponder;
45 + useInputListener = require('react-events/input').useInputListener;
46 + PressResponder = require('react-events/press').PressResponder;
47 + usePressListener = require('react-events/press').usePressListener;
48 };
49
50 describe('Input event responder', () => {
@@ -66,15 +70,17 @@ describe('Input event responder', () => {
70 onChange = jest.fn();
71 onValueChange = jest.fn();
72 ref = React.createRef();
69 - const element = (
70 - <Input
71 - disabled={true}
72 - onChange={onChange}
73 - onValueChange={onValueChange}>
74 - <input ref={ref} />
75 - </Input>
76 - );
77 - ReactDOM.render(element, container);
73 +
74 + function Component() {
75 + useInputListener({
76 + onChange,
77 + onValueChange,
78 + });
79 + return (
80 + <input ref={ref} responders={<InputResponder disabled={true} />} />
81 + );
82 + }
83 + ReactDOM.render(<Component />, container);
84 });
85
86 it('prevents custom events being dispatched', () => {
@@ -113,12 +119,22 @@ describe('Input event responder', () => {
119 onValueChangeCalled++;
120 }
121
116 - ReactDOM.render(
117 - <Input onChange={onChange} onValueChange={onValueChange}>
118 - <input type="text" ref={ref} defaultValue="foo" />
119 - </Input>,
120 - container,
121 - );
122 + function Component() {
123 + useInputListener({
124 + onChange,
125 + onValueChange,
126 + });
127 + return (
128 + <input
129 + type="text"
130 + ref={ref}
131 + defaultValue="foo"
132 + responders={<InputResponder />}
133 + />
134 + );
135 + }
136 + ReactDOM.render(<Component />, container);
137 +
138 ref.current.dispatchEvent(
139 new Event('change', {bubbles: true, cancelable: true}),
140 );
@@ -143,12 +159,21 @@ describe('Input event responder', () => {
159 onValueChangeCalled++;
160 }
161
146 - ReactDOM.render(
147 - <Input onChange={onChange} onValueChange={onValueChange}>
148 - <input type="checkbox" ref={ref} defaultChecked={true} />
149 - </Input>,
150 - container,
151 - );
162 + function Component() {
163 + useInputListener({
164 + onChange,
165 + onValueChange,
166 + });
167 + return (
168 + <input
169 + type="checkbox"
170 + ref={ref}
171 + defaultChecked={true}
172 + responders={<InputResponder />}
173 + />
174 + );
175 + }
176 + ReactDOM.render(<Component />, container);
177
178 // Secretly, set `checked` to false, so that dispatching the `click` will
179 // make it `true` again. Thus, at the time of the event, React should not
@@ -177,12 +202,21 @@ describe('Input event responder', () => {
202 onValueChangeCalled++;
203 }
204
180 - ReactDOM.render(
181 - <Input onChange={onChange} onValueChange={onValueChange}>
182 - <input type="checkbox" ref={ref} defaultChecked={false} />
183 - </Input>,
184 - container,
185 - );
205 + function Component() {
206 + useInputListener({
207 + onChange,
208 + onValueChange,
209 + });
210 + return (
211 + <input
212 + type="checkbox"
213 + ref={ref}
214 + defaultChecked={false}
215 + responders={<InputResponder />}
216 + />
217 + );
218 + }
219 + ReactDOM.render(<Component />, container);
220
221 // Secretly, set `checked` to true, so that dispatching the `click` will
222 // make it `false` again. Thus, at the time of the event, React should not
@@ -210,12 +244,16 @@ describe('Input event responder', () => {
244 onValueChangeCalled++;
245 }
246
213 - ReactDOM.render(
214 - <Input onChange={onChange} onValueChange={onValueChange}>
215 - <input type="checkbox" ref={ref} />
216 - </Input>,
217 - container,
218 - );
247 + function Component() {
248 + useInputListener({
249 + onChange,
250 + onValueChange,
251 + });
252 + return (
253 + <input type="checkbox" ref={ref} responders={<InputResponder />} />
254 + );
255 + }
256 + ReactDOM.render(<Component />, container);
257
258 ref.current.dispatchEvent(
259 new MouseEvent('click', {bubbles: true, cancelable: true}),
@@ -249,12 +287,21 @@ describe('Input event responder', () => {
287 onValueChangeCalled++;
288 }
289
252 - ReactDOM.render(
253 - <Input onChange={onChange} onValueChange={onValueChange}>
254 - <input type="text" defaultValue="foo" ref={ref} />
255 - </Input>,
256 - container,
257 - );
290 + function Component() {
291 + useInputListener({
292 + onChange,
293 + onValueChange,
294 + });
295 + return (
296 + <input
297 + type="text"
298 + defaultValue="foo"
299 + ref={ref}
300 + responders={<InputResponder />}
301 + />
302 + );
303 + }
304 + ReactDOM.render(<Component />, container);
305
306 // Set it programmatically.
307 ref.current.value = 'bar';
@@ -302,12 +349,21 @@ describe('Input event responder', () => {
349 onValueChangeCalled++;
350 }
351
305 - ReactDOM.render(
306 - <Input onChange={onChange} onValueChange={onValueChange}>
307 - <input type="text" defaultValue="42" ref={ref} />
308 - </Input>,
309 - container,
310 - );
352 + function Component() {
353 + useInputListener({
354 + onChange,
355 + onValueChange,
356 + });
357 + return (
358 + <input
359 + type="text"
360 + defaultValue="42"
361 + ref={ref}
362 + responders={<InputResponder />}
363 + />
364 + );
365 + }
366 + ReactDOM.render(<Component />, container);
367
368 // When we set `value` as a property, React updates the "current" value
369 // that it tracks internally. The "current" value is later used to determine
@@ -338,12 +394,21 @@ describe('Input event responder', () => {
394 onValueChangeCalled++;
395 }
396
341 - ReactDOM.render(
342 - <Input onChange={onChange} onValueChange={onValueChange}>
343 - <input type="checkbox" defaultChecked={false} ref={ref} />
344 - </Input>,
345 - container,
346 - );
397 + function Component() {
398 + useInputListener({
399 + onChange,
400 + onValueChange,
401 + });
402 + return (
403 + <input
404 + type="checkbox"
405 + defaultChecked={false}
406 + ref={ref}
407 + responders={<InputResponder />}
408 + />
409 + );
410 + }
411 + ReactDOM.render(<Component />, container);
412
413 // Set the value, updating the "current" value that React tracks to true.
414 ref.current.checked = true;
@@ -382,12 +447,14 @@ describe('Input event responder', () => {
447 onValueChangeCalled++;
448 }
449
385 - ReactDOM.render(
386 - <Input onChange={onChange} onValueChange={onValueChange}>
387 - <input type="radio" ref={ref} />
388 - </Input>,
389 - container,
390 - );
450 + function Component() {
451 + useInputListener({
452 + onChange,
453 + onValueChange,
454 + });
455 + return <input type="radio" ref={ref} responders={<InputResponder />} />;
456 + }
457 + ReactDOM.render(<Component />, container);
458
459 setUntrackedChecked.call(ref.current, true);
460 ref.current.dispatchEvent(
@@ -425,17 +492,36 @@ describe('Input event responder', () => {
492 onValueChangeCalled2++;
493 }
494
428 - ReactDOM.render(
429 - <div ref={ref}>
430 - <Input onChange={onChange1} onValueChange={onValueChange1}>
431 - <input type="radio" name="group" />
432 - </Input>
433 - <Input onChange={onChange2} onValueChange={onValueChange2}>
434 - <input type="radio" name="group" />
435 - </Input>
436 - </div>,
437 - container,
438 - );
495 + function Radio1() {
496 + useInputListener({
497 + onChange: onChange1,
498 + onValueChange: onValueChange1,
499 + });
500 + return (
501 + <input type="radio" name="group" responders={<InputResponder />} />
502 + );
503 + }
504 +
505 + function Radio2() {
506 + useInputListener({
507 + onChange: onChange2,
508 + onValueChange: onValueChange2,
509 + });
510 + return (
511 + <input type="radio" name="group" responders={<InputResponder />} />
512 + );
513 + }
514 +
515 + function Component() {
516 + return (
517 + <div ref={ref}>
518 + <Radio1 />
519 + <Radio2 />
520 + </div>
521 + );
522 + }
523 + ReactDOM.render(<Component />, container);
524 +
525 const option1 = ref.current.childNodes[0];
526 const option2 = ref.current.childNodes[1];
527
@@ -485,12 +571,21 @@ describe('Input event responder', () => {
571 ['text', 'number', 'range'].forEach(type => {
572 onChangeCalled = 0;
573 onValueChangeCalled = 0;
488 - ReactDOM.render(
489 - <Input onChange={onChange} onValueChange={onValueChange}>
490 - <input type={type} name="group" ref={ref} />
491 - </Input>,
492 - container,
493 - );
574 + function Component() {
575 + useInputListener({
576 + onChange,
577 + onValueChange,
578 + });
579 + return (
580 + <input
581 + type={type}
582 + name="group"
583 + ref={ref}
584 + responders={<InputResponder />}
585 + />
586 + );
587 + }
588 + ReactDOM.render(<Component />, container);
589 // Should be ignored (no change):
590 ref.current.dispatchEvent(
591 new Event('change', {bubbles: true, cancelable: true}),
@@ -509,12 +604,16 @@ describe('Input event responder', () => {
604
605 onChangeCalled = 0;
606 onValueChangeCalled = 0;
512 - ReactDOM.render(
513 - <Input onChange={onChange} onValueChange={onValueChange}>
514 - <input type={type} ref={ref} />
515 - </Input>,
516 - container,
517 - );
607 + function Component2() {
608 + useInputListener({
609 + onChange,
610 + onValueChange,
611 + });
612 + return (
613 + <input type={type} ref={ref} responders={<InputResponder />} />
614 + );
615 + }
616 + ReactDOM.render(<Component2 />, container);
617 // Should be ignored (no change):
618 ref.current.dispatchEvent(
619 new Event('input', {bubbles: true, cancelable: true}),
@@ -533,12 +632,16 @@ describe('Input event responder', () => {
632
633 onChangeCalled = 0;
634 onValueChangeCalled = 0;
536 - ReactDOM.render(
537 - <Input onChange={onChange} onValueChange={onValueChange}>
538 - <input type={type} ref={ref} />
539 - </Input>,
540 - container,
541 - );
635 + function Component3() {
636 + useInputListener({
637 + onChange,
638 + onValueChange,
639 + });
640 + return (
641 + <input type={type} ref={ref} responders={<InputResponder />} />
642 + );
643 + }
644 + ReactDOM.render(<Component3 />, container);
645 // Should be ignored (no change):
646 ref.current.dispatchEvent(
647 new Event('change', {bubbles: true, cancelable: true}),
@@ -571,12 +674,14 @@ describe('Input event responder', () => {
674 onValueChangeCalled++;
675 }
676
574 - ReactDOM.render(
575 - <Input onChange={onChange} onValueChange={onValueChange}>
576 - <input type="range" ref={ref} />
577 - </Input>,
578 - container,
579 - );
677 + function Component() {
678 + useInputListener({
679 + onChange,
680 + onValueChange,
681 + });
682 + return <input type="range" ref={ref} responders={<InputResponder />} />;
683 + }
684 + ReactDOM.render(<Component />, container);
685
686 setUntrackedValue.call(ref.current, 10);
687 ref.current.dispatchEvent(
@@ -606,12 +711,15 @@ describe('Input event responder', () => {
711 onValueChangeCalled++;
712 }
713
609 - ReactDOM.render(
610 - <Input onChange={onChange} onValueChange={onValueChange}>
611 - <input type="range" ref={ref} />
612 - </Input>,
613 - container,
614 - );
714 + function Component() {
715 + useInputListener({
716 + onChange,
717 + onValueChange,
718 + });
719 + return <input type="range" ref={ref} responders={<InputResponder />} />;
720 + }
721 + ReactDOM.render(<Component />, container);
722 +
723 setUntrackedValue.call(ref.current, '40');
724 ref.current.dispatchEvent(
725 new Event('input', {bubbles: true, cancelable: true}),
@@ -649,16 +757,12 @@ describe('Input event responder', () => {
757 const div = document.createElement('div');
758 // Mount
759 ReactDOM.render(
652 - <Input>
653 - <input type="text" ref={ref} />
654 - </Input>,
760 + <input type="text" ref={ref} responders={<InputResponder />} />,
761 div,
762 );
763 // Update
764 ReactDOM.render(
659 - <Input>
660 - <input type="text" ref={ref} />
661 - </Input>,
765 + <input type="text" ref={ref} responders={<InputResponder />} />,
766 div,
767 );
768 // Change
@@ -679,6 +783,20 @@ describe('Input event responder', () => {
783
784 let ops = [];
785
786 + function Component({innerRef, onChange, controlledValue}) {
787 + useInputListener({
788 + onChange,
789 + });
790 + return (
791 + <input
792 + type="text"
793 + ref={innerRef}
794 + value={controlledValue}
795 + responders={<InputResponder />}
796 + />
797 + );
798 + }
799 +
800 class ControlledInput extends React.Component {
801 state = {value: 'initial'};
802 onChange = event => this.setState({value: event.target.value});
@@ -687,13 +805,11 @@ describe('Input event responder', () => {
805 const controlledValue =
806 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
807 return (
690 - <Input onChange={this.onChange}>
691 - <input
692 - type="text"
693 - ref={el => (input = el)}
694 - value={controlledValue}
695 - />
696 - </Input>
808 + <Component
809 + onChange={this.onChange}
810 + innerRef={el => (input = el)}
811 + controlledValue={controlledValue}
812 + />
813 );
814 }
815 }
@@ -727,6 +843,20 @@ describe('Input event responder', () => {
843
844 let ops = [];
845
846 + function Component({innerRef, onChange, controlledValue}) {
847 + useInputListener({
848 + onChange,
849 + });
850 + return (
851 + <input
852 + type="checkbox"
853 + ref={innerRef}
854 + checked={controlledValue}
855 + responders={<InputResponder />}
856 + />
857 + );
858 + }
859 +
860 class ControlledInput extends React.Component {
861 state = {checked: false};
862 onChange = event => {
@@ -738,13 +868,11 @@ describe('Input event responder', () => {
868 ? !this.state.checked
869 : this.state.checked;
870 return (
741 - <Input onChange={this.onChange}>
742 - <input
743 - type="checkbox"
744 - ref={el => (input = el)}
745 - checked={controlledValue}
746 - />
747 - </Input>
871 + <Component
872 + controlledValue={controlledValue}
873 + onChange={this.onChange}
874 + innerRef={el => (input = el)}
875 + />
876 );
877 }
878 }
@@ -790,6 +918,20 @@ describe('Input event responder', () => {
918
919 let ops = [];
920
921 + function Component({innerRef, onChange, controlledValue}) {
922 + useInputListener({
923 + onChange,
924 + });
925 + return (
926 + <textarea
927 + type="text"
928 + ref={innerRef}
929 + value={controlledValue}
930 + responders={<InputResponder />}
931 + />
932 + );
933 + }
934 +
935 class ControlledTextarea extends React.Component {
936 state = {value: 'initial'};
937 onChange = event => this.setState({value: event.target.value});
@@ -798,13 +940,11 @@ describe('Input event responder', () => {
940 const controlledValue =
941 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
942 return (
801 - <Input onChange={this.onChange}>
802 - <textarea
803 - type="text"
804 - ref={el => (textarea = el)}
805 - value={controlledValue}
806 - />
807 - </Input>
943 + <Component
944 + onChange={this.onChange}
945 + innerRef={el => (textarea = el)}
946 + controlledValue={controlledValue}
947 + />
948 );
949 }
950 }
@@ -838,6 +978,33 @@ describe('Input event responder', () => {
978
979 let ops = [];
980
981 + function Component({innerRef, onChange, controlledValue}) {
982 + useInputListener({
983 + onChange,
984 + });
985 + return (
986 + <input
987 + type="text"
988 + ref={innerRef}
989 + value={controlledValue}
990 + responders={[<InputResponder />, <PressResponder />]}
991 + />
992 + );
993 + }
994 +
995 + function PressWrapper({innerRef, onPress, onChange, controlledValue}) {
996 + usePressListener({
997 + onPress,
998 + });
999 + return (
1000 + <Component
1001 + onChange={onChange}
1002 + innerRef={el => (input = el)}
1003 + controlledValue={controlledValue}
1004 + />
1005 + );
1006 + }
1007 +
1008 class ControlledInput extends React.Component {
1009 state = {value: 'initial'};
1010 onChange = event => this.setState({value: event.target.value});
@@ -849,15 +1016,12 @@ describe('Input event responder', () => {
1016 const controlledValue =
1017 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
1018 return (
852 - <Press onPress={this.reset}>
853 - <Input onChange={this.onChange}>
854 - <input
855 - type="text"
856 - ref={el => (input = el)}
857 - value={controlledValue}
858 - />
859 - </Input>
860 - </Press>
1019 + <PressWrapper
1020 + onPress={this.reset}
1021 + onChange={this.onChange}
1022 + innerRef={el => (input = el)}
1023 + controlledValue={controlledValue}
1024 + />
1025 );
1026 }
1027 }
@@ -895,6 +1059,6 @@ describe('Input event responder', () => {
1059 });
1060
1061 it('expect displayName to show up for event component', () => {
898 - expect(Input.responder.displayName).toBe('Input');
1062 + expect(InputResponder.displayName).toBe('Input');
1063 });
1064 });
packages/react-events/src/dom/__tests__/Press-test.internal.js
+954 -725
@@ -12,7 +12,8 @@
12 let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15 -let Press;
15 +let PressResponder;
16 +let usePressListener;
17 let Scheduler;
18
19 const DEFAULT_LONG_PRESS_DELAY = 500;
@@ -54,7 +55,8 @@ function init() {
55 ReactFeatureFlags.enableFlareAPI = true;
56 React = require('react');
57 ReactDOM = require('react-dom');
57 - Press = require('react-events/press').Press;
58 + PressResponder = require('react-events/press').PressResponder;
59 + usePressListener = require('react-events/press').usePressListener;
60 Scheduler = require('scheduler');
61 }
62
@@ -82,16 +84,17 @@ describe('Event responder: Press', () => {
84 onPress = jest.fn();
85 onPressEnd = jest.fn();
86 ref = React.createRef();
85 - const element = (
86 - <Press
87 - disabled={true}
88 - onPressStart={onPressStart}
89 - onPress={onPress}
90 - onPressEnd={onPressEnd}>
91 - <div ref={ref} />
92 - </Press>
93 - );
94 - ReactDOM.render(element, container);
87 + const Component = () => {
88 + usePressListener({
89 + onPressStart,
90 + onPress,
91 + onPressEnd,
92 + });
93 + return (
94 + <div ref={ref} responders={<PressResponder disabled={true} />} />
95 + );
96 + };
97 + ReactDOM.render(<Component />, container);
98 });
99
100 it('prevents custom events being dispatched', () => {
@@ -109,12 +112,13 @@ describe('Event responder: Press', () => {
112 beforeEach(() => {
113 onPressStart = jest.fn();
114 ref = React.createRef();
112 - const element = (
113 - <Press onPressStart={onPressStart}>
114 - <div ref={ref} />
115 - </Press>
116 - );
117 - ReactDOM.render(element, container);
115 + const Component = () => {
116 + usePressListener({
117 + onPressStart,
118 + });
119 + return <div ref={ref} responders={<PressResponder />} />;
120 + };
121 + ReactDOM.render(<Component />, container);
122 });
123
124 it('is called after "pointerdown" event', () => {
@@ -261,12 +265,18 @@ describe('Event responder: Press', () => {
265
266 describe('delayPressStart', () => {
267 it('can be configured', () => {
264 - const element = (
265 - <Press delayPressStart={2000} onPressStart={onPressStart}>
266 - <div ref={ref} />
267 - </Press>
268 - );
269 - ReactDOM.render(element, container);
268 + const Component = () => {
269 + usePressListener({
270 + onPressStart,
271 + });
272 + return (
273 + <div
274 + ref={ref}
275 + responders={<PressResponder delayPressStart={2000} />}
276 + />
277 + );
278 + };
279 + ReactDOM.render(<Component />, container);
280
281 ref.current.dispatchEvent(createEvent('pointerdown'));
282 jest.advanceTimersByTime(1999);
@@ -276,12 +286,18 @@ describe('Event responder: Press', () => {
286 });
287
288 it('is cut short if the press is released during a delay', () => {
279 - const element = (
280 - <Press delayPressStart={2000} onPressStart={onPressStart}>
281 - <div ref={ref} />
282 - </Press>
283 - );
284 - ReactDOM.render(element, container);
289 + const Component = () => {
290 + usePressListener({
291 + onPressStart,
292 + });
293 + return (
294 + <div
295 + ref={ref}
296 + responders={<PressResponder delayPressStart={2000} />}
297 + />
298 + );
299 + };
300 + ReactDOM.render(<Component />, container);
301
302 ref.current.getBoundingClientRect = () => ({
303 top: 50,
@@ -305,24 +321,36 @@ describe('Event responder: Press', () => {
321 });
322
323 it('onPressStart is called synchronously if delay is 0ms', () => {
308 - const element = (
309 - <Press delayPressStart={0} onPressStart={onPressStart}>
310 - <div ref={ref} />
311 - </Press>
312 - );
313 - ReactDOM.render(element, container);
324 + const Component = () => {
325 + usePressListener({
326 + onPressStart,
327 + });
328 + return (
329 + <div
330 + ref={ref}
331 + responders={<PressResponder delayPressStart={0} />}
332 + />
333 + );
334 + };
335 + ReactDOM.render(<Component />, container);
336
337 ref.current.dispatchEvent(createEvent('pointerdown'));
338 expect(onPressStart).toHaveBeenCalledTimes(1);
339 });
340
341 it('onPressStart should not be called if pointerCancel is fired before delayPressStart is finished', () => {
320 - const element = (
321 - <Press delayPressStart={500} onPressStart={onPressStart}>
322 - <div ref={ref} />
323 - </Press>
324 - );
325 - ReactDOM.render(element, container);
342 + const Component = () => {
343 + usePressListener({
344 + onPressStart,
345 + });
346 + return (
347 + <div
348 + ref={ref}
349 + responders={<PressResponder delayPressStart={500} />}
350 + />
351 + );
352 + };
353 + ReactDOM.render(<Component />, container);
354
355 ref.current.dispatchEvent(createEvent('pointerdown'));
356 jest.advanceTimersByTime(499);
@@ -338,12 +366,18 @@ describe('Event responder: Press', () => {
366 // This test makes sure that onPressStart is called each time a press
367 // starts, even if a delayPressEnd is delaying the deactivation of the
368 // previous press.
341 - const element = (
342 - <Press delayPressEnd={2000} onPressStart={onPressStart}>
343 - <div ref={ref} />
344 - </Press>
345 - );
346 - ReactDOM.render(element, container);
369 + const Component = () => {
370 + usePressListener({
371 + onPressStart,
372 + });
373 + return (
374 + <div
375 + ref={ref}
376 + responders={<PressResponder delayPressEnd={2000} />}
377 + />
378 + );
379 + };
380 + ReactDOM.render(<Component />, container);
381
382 ref.current.dispatchEvent(createEvent('pointerdown'));
383 ref.current.dispatchEvent(createEvent('pointerup'));
@@ -359,12 +393,13 @@ describe('Event responder: Press', () => {
393 beforeEach(() => {
394 onPressEnd = jest.fn();
395 ref = React.createRef();
362 - const element = (
363 - <Press onPressEnd={onPressEnd}>
364 - <div ref={ref} />
365 - </Press>
366 - );
367 - ReactDOM.render(element, container);
396 + const Component = () => {
397 + usePressListener({
398 + onPressEnd,
399 + });
400 + return <div ref={ref} responders={<PressResponder />} />;
401 + };
402 + ReactDOM.render(<Component />, container);
403 });
404
405 it('is called after "pointerup" event', () => {
@@ -517,12 +552,18 @@ describe('Event responder: Press', () => {
552
553 describe('delayPressEnd', () => {
554 it('can be configured', () => {
520 - const element = (
521 - <Press delayPressEnd={2000} onPressEnd={onPressEnd}>
522 - <div ref={ref} />
523 - </Press>
524 - );
525 - ReactDOM.render(element, container);
555 + const Component = () => {
556 + usePressListener({
557 + onPressEnd,
558 + });
559 + return (
560 + <div
561 + ref={ref}
562 + responders={<PressResponder delayPressEnd={2000} />}
563 + />
564 + );
565 + };
566 + ReactDOM.render(<Component />, container);
567
568 ref.current.dispatchEvent(createEvent('pointerdown'));
569 ref.current.dispatchEvent(createEvent('pointerup'));
@@ -533,12 +574,18 @@ describe('Event responder: Press', () => {
574 });
575
576 it('is reset if "pointerdown" is dispatched during a delay', () => {
536 - const element = (
537 - <Press delayPressEnd={500} onPressEnd={onPressEnd}>
538 - <div ref={ref} />
539 - </Press>
540 - );
541 - ReactDOM.render(element, container);
577 + const Component = () => {
578 + usePressListener({
579 + onPressEnd,
580 + });
581 + return (
582 + <div
583 + ref={ref}
584 + responders={<PressResponder delayPressEnd={500} />}
585 + />
586 + );
587 + };
588 + ReactDOM.render(<Component />, container);
589
590 ref.current.dispatchEvent(createEvent('pointerdown'));
591 ref.current.dispatchEvent(createEvent('pointerup'));
@@ -553,12 +600,15 @@ describe('Event responder: Press', () => {
600 });
601
602 it('onPressEnd is called synchronously if delay is 0ms', () => {
556 - const element = (
557 - <Press delayPressEnd={0} onPressEnd={onPressEnd}>
558 - <div ref={ref} />
559 - </Press>
560 - );
561 - ReactDOM.render(element, container);
603 + const Component = () => {
604 + usePressListener({
605 + onPressEnd,
606 + });
607 + return (
608 + <div ref={ref} responders={<PressResponder delayPressEnd={0} />} />
609 + );
610 + };
611 + ReactDOM.render(<Component />, container);
612
613 ref.current.dispatchEvent(createEvent('pointerdown'));
614 ref.current.dispatchEvent(createEvent('pointerup'));
@@ -572,12 +622,13 @@ describe('Event responder: Press', () => {
622 beforeEach(() => {
623 onPressChange = jest.fn();
624 ref = React.createRef();
575 - const element = (
576 - <Press onPressChange={onPressChange}>
577 - <div ref={ref} />
578 - </Press>
579 - );
580 - ReactDOM.render(element, container);
625 + const Component = () => {
626 + usePressListener({
627 + onPressChange,
628 + });
629 + return <div ref={ref} responders={<PressResponder />} />;
630 + };
631 + ReactDOM.render(<Component />, container);
632 });
633
634 it('is called after "pointerdown" and "pointerup" events', () => {
@@ -599,12 +650,18 @@ describe('Event responder: Press', () => {
650 });
651
652 it('is called after delayed onPressStart', () => {
602 - const element = (
603 - <Press delayPressStart={500} onPressChange={onPressChange}>
604 - <div ref={ref} />
605 - </Press>
606 - );
607 - ReactDOM.render(element, container);
653 + const Component = () => {
654 + usePressListener({
655 + onPressChange,
656 + });
657 + return (
658 + <div
659 + ref={ref}
660 + responders={<PressResponder delayPressStart={500} />}
661 + />
662 + );
663 + };
664 + ReactDOM.render(<Component />, container);
665
666 ref.current.dispatchEvent(createEvent('pointerdown'));
667 jest.advanceTimersByTime(499);
@@ -615,12 +672,18 @@ describe('Event responder: Press', () => {
672 });
673
674 it('is called after delayPressStart is cut short', () => {
618 - const element = (
619 - <Press delayPressStart={500} onPressChange={onPressChange}>
620 - <div ref={ref} />
621 - </Press>
622 - );
623 - ReactDOM.render(element, container);
675 + const Component = () => {
676 + usePressListener({
677 + onPressChange,
678 + });
679 + return (
680 + <div
681 + ref={ref}
682 + responders={<PressResponder delayPressStart={500} />}
683 + />
684 + );
685 + };
686 + ReactDOM.render(<Component />, container);
687
688 ref.current.getBoundingClientRect = () => ({
689 top: 50,
@@ -644,12 +707,15 @@ describe('Event responder: Press', () => {
707 });
708
709 it('is called after delayed onPressEnd', () => {
647 - const element = (
648 - <Press delayPressEnd={500} onPressChange={onPressChange}>
649 - <div ref={ref} />
650 - </Press>
651 - );
652 - ReactDOM.render(element, container);
710 + const Component = () => {
711 + usePressListener({
712 + onPressChange,
713 + });
714 + return (
715 + <div ref={ref} responders={<PressResponder delayPressEnd={500} />} />
716 + );
717 + };
718 + ReactDOM.render(<Component />, container);
719
720 ref.current.dispatchEvent(createEvent('pointerdown'));
721 expect(onPressChange).toHaveBeenCalledTimes(1);
@@ -695,12 +761,13 @@ describe('Event responder: Press', () => {
761 beforeEach(() => {
762 onPress = jest.fn();
763 ref = React.createRef();
698 - const element = (
699 - <Press onPress={onPress}>
700 - <div ref={ref} />
701 - </Press>
702 - );
703 - ReactDOM.render(element, container);
764 + const Component = () => {
765 + usePressListener({
766 + onPress,
767 + });
768 + return <div ref={ref} responders={<PressResponder />} />;
769 + };
770 + ReactDOM.render(<Component />, container);
771 ref.current.getBoundingClientRect = () => ({
772 top: 0,
773 left: 0,
@@ -741,12 +808,13 @@ describe('Event responder: Press', () => {
808 });
809
810 it('is not called after auxillary-button press', () => {
744 - const element = (
745 - <Press onPress={onPress}>
746 - <div ref={ref} />
747 - </Press>
748 - );
749 - ReactDOM.render(element, container);
811 + const Component = () => {
812 + usePressListener({
813 + onPress,
814 + });
815 + return <div ref={ref} responders={<PressResponder />} />;
816 + };
817 + ReactDOM.render(<Component />, container);
818
819 ref.current.dispatchEvent(createEvent('pointerdown', {button: 1}));
820 ref.current.dispatchEvent(
@@ -766,12 +834,13 @@ describe('Event responder: Press', () => {
834
835 it('is not called after invalid "keyup" event', () => {
836 const inputRef = React.createRef();
769 - const element = (
770 - <Press onPress={onPress}>
771 - <input ref={inputRef} />
772 - </Press>
773 - );
774 - ReactDOM.render(element, container);
837 + const Component = () => {
838 + usePressListener({
839 + onPress,
840 + });
841 + return <input ref={inputRef} responders={<PressResponder />} />;
842 + };
843 + ReactDOM.render(<Component />, container);
844 inputRef.current.dispatchEvent(
845 createKeyboardEvent('keydown', {key: 'Enter'}),
846 );
@@ -786,12 +855,15 @@ describe('Event responder: Press', () => {
855 });
856
857 it('is always called immediately after press is released', () => {
789 - const element = (
790 - <Press delayPressEnd={500} onPress={onPress}>
791 - <div ref={ref} />
792 - </Press>
793 - );
794 - ReactDOM.render(element, container);
858 + const Component = () => {
859 + usePressListener({
860 + onPress,
861 + });
862 + return (
863 + <div ref={ref} responders={<PressResponder delayPressEnd={500} />} />
864 + );
865 + };
866 + ReactDOM.render(<Component />, container);
867
868 ref.current.dispatchEvent(createEvent('pointerdown'));
869 ref.current.dispatchEvent(
@@ -819,14 +891,19 @@ describe('Event responder: Press', () => {
891 it('is called if target rect is not right but the target is (for mouse events)', () => {
892 const buttonRef = React.createRef();
893 const divRef = React.createRef();
822 - const element = (
823 - <Press onPress={onPress}>
824 - <div ref={divRef}>
894 +
895 + const Component = () => {
896 + usePressListener({
897 + onPress,
898 + });
899 + return (
900 + <div ref={divRef} responders={<PressResponder />}>
901 <button ref={buttonRef} />
902 </div>
827 - </Press>
828 - );
829 - ReactDOM.render(element, container);
903 + );
904 + };
905 + ReactDOM.render(<Component />, container);
906 +
907 divRef.current.getBoundingClientRect = () => ({
908 left: 0,
909 right: 0,
@@ -857,12 +934,18 @@ describe('Event responder: Press', () => {
934 beforeEach(() => {
935 onLongPress = jest.fn();
936 ref = React.createRef();
860 - const element = (
861 - <Press onLongPress={onLongPress}>
862 - <div ref={ref} />
863 - </Press>
864 - );
865 - ReactDOM.render(element, container);
937 + const Component = () => {
938 + usePressListener({
939 + onLongPress,
940 + });
941 + return (
942 + <div
943 + ref={ref}
944 + responders={<PressResponder enableLongPress={true} />}
945 + />
946 + );
947 + };
948 + ReactDOM.render(<Component />, container);
949 });
950
951 it('is called if "pointerdown" lasts default delay', () => {
@@ -929,12 +1012,20 @@ describe('Event responder: Press', () => {
1012
1013 describe('delayLongPress', () => {
1014 it('can be configured', () => {
932 - const element = (
933 - <Press delayLongPress={2000} onLongPress={onLongPress}>
934 - <div ref={ref} />
935 - </Press>
936 - );
937 - ReactDOM.render(element, container);
1015 + const Component = () => {
1016 + usePressListener({
1017 + onLongPress,
1018 + });
1019 + return (
1020 + <div
1021 + ref={ref}
1022 + responders={
1023 + <PressResponder delayLongPress={2000} enableLongPress={true} />
1024 + }
1025 + />
1026 + );
1027 + };
1028 + ReactDOM.render(<Component />, container);
1029
1030 ref.current.dispatchEvent(createEvent('pointerdown'));
1031 jest.advanceTimersByTime(1999);
@@ -944,12 +1035,20 @@ describe('Event responder: Press', () => {
1035 });
1036
1037 it('uses 10ms minimum delay length', () => {
947 - const element = (
948 - <Press delayLongPress={0} onLongPress={onLongPress}>
949 - <div ref={ref} />
950 - </Press>
951 - );
952 - ReactDOM.render(element, container);
1038 + const Component = () => {
1039 + usePressListener({
1040 + onLongPress,
1041 + });
1042 + return (
1043 + <div
1044 + ref={ref}
1045 + responders={
1046 + <PressResponder delayLongPress={0} enableLongPress={true} />
1047 + }
1048 + />
1049 + );
1050 + };
1051 + ReactDOM.render(<Component />, container);
1052
1053 ref.current.dispatchEvent(createEvent('pointerdown'));
1054 jest.advanceTimersByTime(9);
@@ -960,12 +1059,23 @@ describe('Event responder: Press', () => {
1059
1060 it('compounds with "delayPressStart"', () => {
1061 const delayPressStart = 100;
963 - const element = (
964 - <Press delayPressStart={delayPressStart} onLongPress={onLongPress}>
965 - <div ref={ref} />
966 - </Press>
967 - );
968 - ReactDOM.render(element, container);
1062 + const Component = () => {
1063 + usePressListener({
1064 + onLongPress,
1065 + });
1066 + return (
1067 + <div
1068 + ref={ref}
1069 + responders={
1070 + <PressResponder
1071 + delayPressStart={delayPressStart}
1072 + enableLongPress={true}
1073 + />
1074 + }
1075 + />
1076 + );
1077 + };
1078 + ReactDOM.render(<Component />, container);
1079
1080 ref.current.dispatchEvent(createEvent('pointerdown'));
1081 jest.advanceTimersByTime(
@@ -982,12 +1092,18 @@ describe('Event responder: Press', () => {
1092 it('is called when long press state changes', () => {
1093 const onLongPressChange = jest.fn();
1094 const ref = React.createRef();
985 - const element = (
986 - <Press onLongPressChange={onLongPressChange}>
987 - <div ref={ref} />
988 - </Press>
989 - );
990 - ReactDOM.render(element, container);
1095 + const Component = () => {
1096 + usePressListener({
1097 + onLongPressChange,
1098 + });
1099 + return (
1100 + <div
1101 + ref={ref}
1102 + responders={<PressResponder enableLongPress={true} />}
1103 + />
1104 + );
1105 + };
1106 + ReactDOM.render(<Component />, container);
1107
1108 ref.current.dispatchEvent(createEvent('pointerdown'));
1109 jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
@@ -1001,12 +1117,20 @@ describe('Event responder: Press', () => {
1117 it('is called after delayed onPressEnd', () => {
1118 const onLongPressChange = jest.fn();
1119 const ref = React.createRef();
1004 - const element = (
1005 - <Press delayPressEnd={500} onLongPressChange={onLongPressChange}>
1006 - <div ref={ref} />
1007 - </Press>
1008 - );
1009 - ReactDOM.render(element, container);
1120 + const Component = () => {
1121 + usePressListener({
1122 + onLongPressChange,
1123 + });
1124 + return (
1125 + <div
1126 + ref={ref}
1127 + responders={
1128 + <PressResponder delayPressEnd={500} enableLongPress={true} />
1129 + }
1130 + />
1131 + );
1132 + };
1133 + ReactDOM.render(<Component />, container);
1134
1135 ref.current.dispatchEvent(createEvent('pointerdown'));
1136 jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
@@ -1021,21 +1145,30 @@ describe('Event responder: Press', () => {
1145 });
1146 });
1147
1024 - describe('onLongPressShouldCancelPress', () => {
1148 + describe('longPressShouldCancelPress', () => {
1149 it('if true it cancels "onPress"', () => {
1150 const onPress = jest.fn();
1151 const onPressChange = jest.fn();
1152 const ref = React.createRef();
1029 - const element = (
1030 - <Press
1031 - onLongPress={() => {}}
1032 - onLongPressShouldCancelPress={() => true}
1033 - onPressChange={onPressChange}
1034 - onPress={onPress}>
1035 - <div ref={ref} />
1036 - </Press>
1037 - );
1038 - ReactDOM.render(element, container);
1153 + const Component = () => {
1154 + usePressListener({
1155 + onLongPress: () => {},
1156 + onPressChange,
1157 + onPress,
1158 + });
1159 + return (
1160 + <div
1161 + ref={ref}
1162 + responders={
1163 + <PressResponder
1164 + longPressShouldCancelPress={() => true}
1165 + enableLongPress={true}
1166 + />
1167 + }
1168 + />
1169 + );
1170 + };
1171 + ReactDOM.render(<Component />, container);
1172
1173 // NOTE: onPressChange behavior should not be affected
1174 ref.current.dispatchEvent(createEvent('pointerdown'));
@@ -1051,12 +1184,13 @@ describe('Event responder: Press', () => {
1184 it('is called after "pointermove"', () => {
1185 const onPressMove = jest.fn();
1186 const ref = React.createRef();
1054 - const element = (
1055 - <Press onPressMove={onPressMove}>
1056 - <div ref={ref} />
1057 - </Press>
1058 - );
1059 - ReactDOM.render(element, container);
1187 + const Component = () => {
1188 + usePressListener({
1189 + onPressMove,
1190 + });
1191 + return <div ref={ref} responders={<PressResponder />} />;
1192 + };
1193 + ReactDOM.render(<Component />, container);
1194
1195 ref.current.getBoundingClientRect = () => ({
1196 top: 0,
@@ -1083,12 +1217,13 @@ describe('Event responder: Press', () => {
1217 it('is not called if "pointermove" occurs during keyboard press', () => {
1218 const onPressMove = jest.fn();
1219 const ref = React.createRef();
1086 - const element = (
1087 - <Press onPressMove={onPressMove}>
1088 - <div ref={ref} />
1089 - </Press>
1090 - );
1091 - ReactDOM.render(element, container);
1220 + const Component = () => {
1221 + usePressListener({
1222 + onPressMove,
1223 + });
1224 + return <div ref={ref} responders={<PressResponder />} />;
1225 + };
1226 + ReactDOM.render(<Component />, container);
1227
1228 ref.current.getBoundingClientRect = () => ({
1229 top: 0,
@@ -1110,12 +1245,13 @@ describe('Event responder: Press', () => {
1245 it('ignores browser emulated events', () => {
1246 const onPressMove = jest.fn();
1247 const ref = React.createRef();
1113 - const element = (
1114 - <Press onPressMove={onPressMove}>
1115 - <div ref={ref} />
1116 - </Press>
1117 - );
1118 - ReactDOM.render(element, container);
1248 + const Component = () => {
1249 + usePressListener({
1250 + onPressMove,
1251 + });
1252 + return <div ref={ref} responders={<PressResponder />} />;
1253 + };
1254 + ReactDOM.render(<Component />, container);
1255
1256 ref.current.getBoundingClientRect = () => ({
1257 top: 0,
@@ -1185,18 +1321,17 @@ describe('Event responder: Press', () => {
1321 events.push(msg);
1322 };
1323
1188 - const element = (
1189 - <Press
1190 - onPress={createEventHandler('onPress')}
1191 - onPressChange={createEventHandler('onPressChange')}
1192 - onPressMove={createEventHandler('onPressMove')}
1193 - onPressStart={createEventHandler('onPressStart')}
1194 - onPressEnd={createEventHandler('onPressEnd')}>
1195 - <div ref={ref} />
1196 - </Press>
1197 - );
1198 -
1199 - ReactDOM.render(element, container);
1324 + const Component = () => {
1325 + usePressListener({
1326 + onPress: createEventHandler('onPress'),
1327 + onPressChange: createEventHandler('onPressChange'),
1328 + onPressMove: createEventHandler('onPressMove'),
1329 + onPressStart: createEventHandler('onPressStart'),
1330 + onPressEnd: createEventHandler('onPressEnd'),
1331 + });
1332 + return <div ref={ref} responders={<PressResponder />} />;
1333 + };
1334 + ReactDOM.render(<Component />, container);
1335
1336 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1337 ref.current.dispatchEvent(createEvent('pointerdown'));
@@ -1224,20 +1359,21 @@ describe('Event responder: Press', () => {
1359 events.push(msg);
1360 };
1361
1227 - const element = (
1228 - <div ref={outerRef}>
1229 - <Press
1230 - onPress={createEventHandler('onPress')}
1231 - onPressChange={createEventHandler('onPressChange')}
1232 - onPressMove={createEventHandler('onPressMove')}
1233 - onPressStart={createEventHandler('onPressStart')}
1234 - onPressEnd={createEventHandler('onPressEnd')}>
1235 - <div ref={innerRef} />
1236 - </Press>
1237 - </div>
1238 - );
1239 -
1240 - ReactDOM.render(element, container);
1362 + const Component = () => {
1363 + usePressListener({
1364 + onPress: createEventHandler('onPress'),
1365 + onPressChange: createEventHandler('onPressChange'),
1366 + onPressMove: createEventHandler('onPressMove'),
1367 + onPressStart: createEventHandler('onPressStart'),
1368 + onPressEnd: createEventHandler('onPressEnd'),
1369 + });
1370 + return (
1371 + <div ref={outerRef}>
1372 + <div ref={innerRef} responders={<PressResponder />} />
1373 + </div>
1374 + );
1375 + };
1376 + ReactDOM.render(<Component />, container);
1377
1378 innerRef.current.getBoundingClientRect = getBoundingClientRectMock;
1379 innerRef.current.dispatchEvent(createEvent('pointerdown'));
@@ -1272,19 +1408,22 @@ describe('Event responder: Press', () => {
1408 events.push(msg);
1409 };
1410
1275 - const element = (
1276 - <Press
1277 - delayPressStart={500}
1278 - onPress={createEventHandler('onPress')}
1279 - onPressChange={createEventHandler('onPressChange')}
1280 - onPressMove={createEventHandler('onPressMove')}
1281 - onPressStart={createEventHandler('onPressStart')}
1282 - onPressEnd={createEventHandler('onPressEnd')}>
1283 - <div ref={ref} />
1284 - </Press>
1285 - );
1286 -
1287 - ReactDOM.render(element, container);
1411 + const Component = () => {
1412 + usePressListener({
1413 + onPress: createEventHandler('onPress'),
1414 + onPressChange: createEventHandler('onPressChange'),
1415 + onPressMove: createEventHandler('onPressMove'),
1416 + onPressStart: createEventHandler('onPressStart'),
1417 + onPressEnd: createEventHandler('onPressEnd'),
1418 + });
1419 + return (
1420 + <div
1421 + ref={ref}
1422 + responders={<PressResponder delayPressStart={500} />}
1423 + />
1424 + );
1425 + };
1426 + ReactDOM.render(<Component />, container);
1427
1428 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1429 ref.current.dispatchEvent(createEvent('pointerdown'));
@@ -1311,19 +1450,25 @@ describe('Event responder: Press', () => {
1450 };
1451 const pressRetentionOffset = {top: 40, bottom: 40, left: 40, right: 40};
1452
1314 - const element = (
1315 - <Press
1316 - pressRetentionOffset={pressRetentionOffset}
1317 - onPress={createEventHandler('onPress')}
1318 - onPressChange={createEventHandler('onPressChange')}
1319 - onPressMove={createEventHandler('onPressMove')}
1320 - onPressStart={createEventHandler('onPressStart')}
1321 - onPressEnd={createEventHandler('onPressEnd')}>
1322 - <div ref={ref} />
1323 - </Press>
1324 - );
1453 + const Component = () => {
1454 + usePressListener({
1455 + onPress: createEventHandler('onPress'),
1456 + onPressChange: createEventHandler('onPressChange'),
1457 + onPressMove: createEventHandler('onPressMove'),
1458 + onPressStart: createEventHandler('onPressStart'),
1459 + onPressEnd: createEventHandler('onPressEnd'),
1460 + });
1461 + return (
1462 + <div
1463 + ref={ref}
1464 + responders={
1465 + <PressResponder pressRetentionOffset={pressRetentionOffset} />
1466 + }
1467 + />
1468 + );
1469 + };
1470 + ReactDOM.render(<Component />, container);
1471
1326 - ReactDOM.render(element, container);
1472 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1473 ref.current.dispatchEvent(createEvent('pointerdown'));
1474 ref.current.dispatchEvent(
@@ -1350,16 +1495,16 @@ describe('Event responder: Press', () => {
1495 events.push(msg);
1496 };
1497
1353 - const element = (
1354 - <Press
1355 - onPress={createEventHandler('onPress')}
1356 - onPressStart={createEventHandler('onPressStart')}
1357 - onPressEnd={createEventHandler('onPressEnd')}>
1358 - <div ref={ref} />
1359 - </Press>
1360 - );
1498 + const Component = () => {
1499 + usePressListener({
1500 + onPress: createEventHandler('onPress'),
1501 + onPressStart: createEventHandler('onPressStart'),
1502 + onPressEnd: createEventHandler('onPressEnd'),
1503 + });
1504 + return <div ref={ref} responders={<PressResponder />} />;
1505 + };
1506 + ReactDOM.render(<Component />, container);
1507
1362 - ReactDOM.render(element, container);
1508 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1509 ref.current.dispatchEvent(createEvent('pointerdown'));
1510 // emulate smaller dimensions change on activation
@@ -1388,16 +1533,16 @@ describe('Event responder: Press', () => {
1533 events.push(msg);
1534 };
1535
1391 - const element = (
1392 - <Press
1393 - onPress={createEventHandler('onPress')}
1394 - onPressStart={createEventHandler('onPressStart')}
1395 - onPressEnd={createEventHandler('onPressEnd')}>
1396 - <div ref={ref} />
1397 - </Press>
1398 - );
1536 + const Component = () => {
1537 + usePressListener({
1538 + onPress: createEventHandler('onPress'),
1539 + onPressStart: createEventHandler('onPressStart'),
1540 + onPressEnd: createEventHandler('onPressEnd'),
1541 + });
1542 + return <div ref={ref} responders={<PressResponder />} />;
1543 + };
1544 + ReactDOM.render(<Component />, container);
1545
1400 - ReactDOM.render(element, container);
1546 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1547 ref.current.dispatchEvent(createEvent('pointerdown'));
1548 // emulate larger dimensions change on activation
@@ -1437,18 +1582,17 @@ describe('Event responder: Press', () => {
1582 events.push(msg);
1583 };
1584
1440 - const element = (
1441 - <Press
1442 - onPress={createEventHandler('onPress')}
1443 - onPressChange={createEventHandler('onPressChange')}
1444 - onPressMove={createEventHandler('onPressMove')}
1445 - onPressStart={createEventHandler('onPressStart')}
1446 - onPressEnd={createEventHandler('onPressEnd')}>
1447 - <div ref={ref} />
1448 - </Press>
1449 - );
1450 -
1451 - ReactDOM.render(element, container);
1585 + const Component = () => {
1586 + usePressListener({
1587 + onPress: createEventHandler('onPress'),
1588 + onPressChange: createEventHandler('onPressChange'),
1589 + onPressMove: createEventHandler('onPressMove'),
1590 + onPressStart: createEventHandler('onPressStart'),
1591 + onPressEnd: createEventHandler('onPressEnd'),
1592 + });
1593 + return <div ref={ref} responders={<PressResponder />} />;
1594 + };
1595 + ReactDOM.render(<Component />, container);
1596
1597 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1598 ref.current.dispatchEvent(createEvent('pointerdown'));
@@ -1475,21 +1619,29 @@ describe('Event responder: Press', () => {
1619 events.push(msg);
1620 };
1621
1478 - const element = (
1479 - <Press
1480 - delayPressStart={500}
1481 - delayPressEnd={500}
1482 - onLongPress={createEventHandler('onLongPress')}
1483 - onPress={createEventHandler('onPress')}
1484 - onPressChange={createEventHandler('onPressChange')}
1485 - onPressMove={createEventHandler('onPressMove')}
1486 - onPressStart={createEventHandler('onPressStart')}
1487 - onPressEnd={createEventHandler('onPressEnd')}>
1488 - <div ref={ref} />
1489 - </Press>
1490 - );
1491 -
1492 - ReactDOM.render(element, container);
1622 + const Component = () => {
1623 + usePressListener({
1624 + onLongPress: createEventHandler('onLongPress'),
1625 + onPress: createEventHandler('onPress'),
1626 + onPressChange: createEventHandler('onPressChange'),
1627 + onPressMove: createEventHandler('onPressMove'),
1628 + onPressStart: createEventHandler('onPressStart'),
1629 + onPressEnd: createEventHandler('onPressEnd'),
1630 + });
1631 + return (
1632 + <div
1633 + ref={ref}
1634 + responders={
1635 + <PressResponder
1636 + delayPressStart={500}
1637 + delayPressEnd={500}
1638 + enableLongPress={true}
1639 + />
1640 + }
1641 + />
1642 + );
1643 + };
1644 + ReactDOM.render(<Component />, container);
1645
1646 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1647 ref.current.dispatchEvent(createEvent('pointerdown'));
@@ -1513,18 +1665,17 @@ describe('Event responder: Press', () => {
1665 events.push(msg);
1666 };
1667
1516 - const element = (
1517 - <Press
1518 - onPress={createEventHandler('onPress')}
1519 - onPressChange={createEventHandler('onPressChange')}
1520 - onPressMove={createEventHandler('onPressMove')}
1521 - onPressStart={createEventHandler('onPressStart')}
1522 - onPressEnd={createEventHandler('onPressEnd')}>
1523 - <div ref={ref} />
1524 - </Press>
1525 - );
1526 -
1527 - ReactDOM.render(element, container);
1668 + const Component = () => {
1669 + usePressListener({
1670 + onPress: createEventHandler('onPress'),
1671 + onPressChange: createEventHandler('onPressChange'),
1672 + onPressMove: createEventHandler('onPressMove'),
1673 + onPressStart: createEventHandler('onPressStart'),
1674 + onPressEnd: createEventHandler('onPressEnd'),
1675 + });
1676 + return <div ref={ref} responders={<PressResponder />} />;
1677 + };
1678 + ReactDOM.render(<Component />, container);
1679
1680 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1681 ref.current.dispatchEvent(
@@ -1568,18 +1719,17 @@ describe('Event responder: Press', () => {
1719 events.push(msg);
1720 };
1721
1571 - const element = (
1572 - <Press
1573 - onPress={createEventHandler('onPress')}
1574 - onPressChange={createEventHandler('onPressChange')}
1575 - onPressMove={createEventHandler('onPressMove')}
1576 - onPressStart={createEventHandler('onPressStart')}
1577 - onPressEnd={createEventHandler('onPressEnd')}>
1578 - <div ref={ref} />
1579 - </Press>
1580 - );
1581 -
1582 - ReactDOM.render(element, container);
1722 + const Component = () => {
1723 + usePressListener({
1724 + onPress: createEventHandler('onPress'),
1725 + onPressChange: createEventHandler('onPressChange'),
1726 + onPressMove: createEventHandler('onPressMove'),
1727 + onPressStart: createEventHandler('onPressStart'),
1728 + onPressEnd: createEventHandler('onPressEnd'),
1729 + });
1730 + return <div ref={ref} responders={<PressResponder />} />;
1731 + };
1732 + ReactDOM.render(<Component />, container);
1733
1734 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1735 ref.current.dispatchEvent(
@@ -1634,18 +1784,17 @@ describe('Event responder: Press', () => {
1784 events.push(msg);
1785 };
1786
1637 - const element = (
1638 - <Press
1639 - onPress={createEventHandler('onPress')}
1640 - onPressChange={createEventHandler('onPressChange')}
1641 - onPressMove={createEventHandler('onPressMove')}
1642 - onPressStart={createEventHandler('onPressStart')}
1643 - onPressEnd={createEventHandler('onPressEnd')}>
1644 - <div ref={ref} />
1645 - </Press>
1646 - );
1647 -
1648 - ReactDOM.render(element, container);
1787 + const Component = () => {
1788 + usePressListener({
1789 + onPress: createEventHandler('onPress'),
1790 + onPressChange: createEventHandler('onPressChange'),
1791 + onPressMove: createEventHandler('onPressMove'),
1792 + onPressStart: createEventHandler('onPressStart'),
1793 + onPressEnd: createEventHandler('onPressEnd'),
1794 + });
1795 + return <div ref={ref} responders={<PressResponder />} />;
1796 + };
1797 + ReactDOM.render(<Component />, container);
1798
1799 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1800 ref.current.dispatchEvent(
@@ -1758,18 +1907,17 @@ describe('Event responder: Press', () => {
1907 events.push(msg);
1908 };
1909
1761 - const element = (
1762 - <Press
1763 - onPress={createEventHandler('onPress')}
1764 - onPressChange={createEventHandler('onPressChange')}
1765 - onPressMove={createEventHandler('onPressMove')}
1766 - onPressStart={createEventHandler('onPressStart')}
1767 - onPressEnd={createEventHandler('onPressEnd')}>
1768 - <div ref={ref} />
1769 - </Press>
1770 - );
1771 -
1772 - ReactDOM.render(element, container);
1910 + const Component = () => {
1911 + usePressListener({
1912 + onPress: createEventHandler('onPress'),
1913 + onPressChange: createEventHandler('onPressChange'),
1914 + onPressMove: createEventHandler('onPressMove'),
1915 + onPressStart: createEventHandler('onPressStart'),
1916 + onPressEnd: createEventHandler('onPressEnd'),
1917 + });
1918 + return <div ref={ref} responders={<PressResponder />} />;
1919 + };
1920 + ReactDOM.render(<Component />, container);
1921
1922 document.elementFromPoint = () => ref.current;
1923 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -1809,19 +1957,22 @@ describe('Event responder: Press', () => {
1957 events.push(msg);
1958 };
1959
1812 - const element = (
1813 - <Press
1814 - delayPressStart={500}
1815 - onPress={createEventHandler('onPress')}
1816 - onPressChange={createEventHandler('onPressChange')}
1817 - onPressMove={createEventHandler('onPressMove')}
1818 - onPressStart={createEventHandler('onPressStart')}
1819 - onPressEnd={createEventHandler('onPressEnd')}>
1820 - <div ref={ref} />
1821 - </Press>
1822 - );
1823 -
1824 - ReactDOM.render(element, container);
1960 + const Component = () => {
1961 + usePressListener({
1962 + onPress: createEventHandler('onPress'),
1963 + onPressChange: createEventHandler('onPressChange'),
1964 + onPressMove: createEventHandler('onPressMove'),
1965 + onPressStart: createEventHandler('onPressStart'),
1966 + onPressEnd: createEventHandler('onPressEnd'),
1967 + });
1968 + return (
1969 + <div
1970 + ref={ref}
1971 + responders={<PressResponder delayPressStart={500} />}
1972 + />
1973 + );
1974 + };
1975 + ReactDOM.render(<Component />, container);
1976
1977 document.elementFromPoint = () => ref.current;
1978 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -1861,21 +2012,26 @@ describe('Event responder: Press', () => {
2012 };
2013 const pressRetentionOffset = {top: 40, bottom: 40, left: 40, right: 40};
2014
1864 - const element = (
1865 - <Press
1866 - pressRetentionOffset={pressRetentionOffset}
1867 - onPress={createEventHandler('onPress')}
1868 - onPressChange={createEventHandler('onPressChange')}
1869 - onPressMove={createEventHandler('onPressMove')}
1870 - onPressStart={createEventHandler('onPressStart')}
1871 - onPressEnd={createEventHandler('onPressEnd')}>
1872 - <div ref={ref} />
1873 - </Press>
1874 - );
1875 -
1876 - ReactDOM.render(element, container);
1877 -
1878 - document.elementFromPoint = () => ref.current;
2015 + const Component = () => {
2016 + usePressListener({
2017 + onPress: createEventHandler('onPress'),
2018 + onPressChange: createEventHandler('onPressChange'),
2019 + onPressMove: createEventHandler('onPressMove'),
2020 + onPressStart: createEventHandler('onPressStart'),
2021 + onPressEnd: createEventHandler('onPressEnd'),
2022 + });
2023 + return (
2024 + <div
2025 + ref={ref}
2026 + responders={
2027 + <PressResponder pressRetentionOffset={pressRetentionOffset} />
2028 + }
2029 + />
2030 + );
2031 + };
2032 + ReactDOM.render(<Component />, container);
2033 +
2034 + document.elementFromPoint = () => ref.current;
2035 ref.current.getBoundingClientRect = getBoundingClientRectMock;
2036 ref.current.dispatchEvent(
2037 createTouchEvent('touchstart', 0, {
@@ -1912,16 +2068,15 @@ describe('Event responder: Press', () => {
2068 events.push(msg);
2069 };
2070
1915 - const element = (
1916 - <Press
1917 - onPress={createEventHandler('onPress')}
1918 - onPressStart={createEventHandler('onPressStart')}
1919 - onPressEnd={createEventHandler('onPressEnd')}>
1920 - <div ref={ref} />
1921 - </Press>
1922 - );
1923 -
1924 - ReactDOM.render(element, container);
2071 + const Component = () => {
2072 + usePressListener({
2073 + onPress: createEventHandler('onPress'),
2074 + onPressStart: createEventHandler('onPressStart'),
2075 + onPressEnd: createEventHandler('onPressEnd'),
2076 + });
2077 + return <div ref={ref} responders={<PressResponder />} />;
2078 + };
2079 + ReactDOM.render(<Component />, container);
2080
2081 document.elementFromPoint = () => ref.current;
2082 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -1966,16 +2121,15 @@ describe('Event responder: Press', () => {
2121 events.push(msg);
2122 };
2123
1969 - const element = (
1970 - <Press
1971 - onPress={createEventHandler('onPress')}
1972 - onPressStart={createEventHandler('onPressStart')}
1973 - onPressEnd={createEventHandler('onPressEnd')}>
1974 - <div ref={ref} />
1975 - </Press>
1976 - );
1977 -
1978 - ReactDOM.render(element, container);
2124 + const Component = () => {
2125 + usePressListener({
2126 + onPress: createEventHandler('onPress'),
2127 + onPressStart: createEventHandler('onPressStart'),
2128 + onPressEnd: createEventHandler('onPressEnd'),
2129 + });
2130 + return <div ref={ref} responders={<PressResponder />} />;
2131 + };
2132 + ReactDOM.render(<Component />, container);
2133
2134 document.elementFromPoint = () => ref.current;
2135 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -2031,18 +2185,17 @@ describe('Event responder: Press', () => {
2185 events.push(msg);
2186 };
2187
2034 - const element = (
2035 - <Press
2036 - onPress={createEventHandler('onPress')}
2037 - onPressChange={createEventHandler('onPressChange')}
2038 - onPressMove={createEventHandler('onPressMove')}
2039 - onPressStart={createEventHandler('onPressStart')}
2040 - onPressEnd={createEventHandler('onPressEnd')}>
2041 - <div ref={ref} />
2042 - </Press>
2043 - );
2044 -
2045 - ReactDOM.render(element, container);
2188 + const Component = () => {
2189 + usePressListener({
2190 + onPress: createEventHandler('onPress'),
2191 + onPressChange: createEventHandler('onPressChange'),
2192 + onPressMove: createEventHandler('onPressMove'),
2193 + onPressStart: createEventHandler('onPressStart'),
2194 + onPressEnd: createEventHandler('onPressEnd'),
2195 + });
2196 + return <div ref={ref} responders={<PressResponder />} />;
2197 + };
2198 + ReactDOM.render(<Component />, container);
2199
2200 document.elementFromPoint = () => ref.current;
2201 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -2088,21 +2241,29 @@ describe('Event responder: Press', () => {
2241 events.push(msg);
2242 };
2243
2091 - const element = (
2092 - <Press
2093 - delayPressStart={500}
2094 - delayPressEnd={500}
2095 - onLongPress={createEventHandler('onLongPress')}
2096 - onPress={createEventHandler('onPress')}
2097 - onPressChange={createEventHandler('onPressChange')}
2098 - onPressMove={createEventHandler('onPressMove')}
2099 - onPressStart={createEventHandler('onPressStart')}
2100 - onPressEnd={createEventHandler('onPressEnd')}>
2101 - <div ref={ref} />
2102 - </Press>
2103 - );
2104 -
2105 - ReactDOM.render(element, container);
2244 + const Component = () => {
2245 + usePressListener({
2246 + onLongPress: createEventHandler('onLongPress'),
2247 + onPress: createEventHandler('onPress'),
2248 + onPressChange: createEventHandler('onPressChange'),
2249 + onPressMove: createEventHandler('onPressMove'),
2250 + onPressStart: createEventHandler('onPressStart'),
2251 + onPressEnd: createEventHandler('onPressEnd'),
2252 + });
2253 + return (
2254 + <div
2255 + ref={ref}
2256 + responders={
2257 + <PressResponder
2258 + delayPressStart={500}
2259 + delayPressEnd={500}
2260 + enableLongPress={true}
2261 + />
2262 + }
2263 + />
2264 + );
2265 + };
2266 + ReactDOM.render(<Component />, container);
2267
2268 document.elementFromPoint = () => ref.current;
2269 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -2145,18 +2306,17 @@ describe('Event responder: Press', () => {
2306 events.push(msg);
2307 };
2308
2148 - const element = (
2149 - <Press
2150 - onPress={createEventHandler('onPress')}
2151 - onPressChange={createEventHandler('onPressChange')}
2152 - onPressMove={createEventHandler('onPressMove')}
2153 - onPressStart={createEventHandler('onPressStart')}
2154 - onPressEnd={createEventHandler('onPressEnd')}>
2155 - <div ref={ref} />
2156 - </Press>
2157 - );
2158 -
2159 - ReactDOM.render(element, container);
2309 + const Component = () => {
2310 + usePressListener({
2311 + onPress: createEventHandler('onPress'),
2312 + onPressChange: createEventHandler('onPressChange'),
2313 + onPressMove: createEventHandler('onPressMove'),
2314 + onPressStart: createEventHandler('onPressStart'),
2315 + onPressEnd: createEventHandler('onPressEnd'),
2316 + });
2317 + return <div ref={ref} responders={<PressResponder />} />;
2318 + };
2319 + ReactDOM.render(<Component />, container);
2320
2321 document.elementFromPoint = () => ref.current;
2322 ref.current.getBoundingClientRect = getBoundingClientRectMock;
@@ -2216,21 +2376,31 @@ describe('Event responder: Press', () => {
2376 events.push(msg);
2377 };
2378
2219 - const element = (
2220 - <Press
2221 - delayPressStart={250}
2222 - delayPressEnd={250}
2223 - onLongPress={createEventHandler('onLongPress')}
2224 - onLongPressChange={createEventHandler('onLongPressChange')}
2225 - onPress={createEventHandler('onPress')}
2226 - onPressChange={createEventHandler('onPressChange')}
2227 - onPressStart={createEventHandler('onPressStart')}
2228 - onPressEnd={createEventHandler('onPressEnd')}>
2229 - <div ref={ref} />
2230 - </Press>
2231 - );
2232 -
2233 - ReactDOM.render(element, container);
2379 + const Component = () => {
2380 + usePressListener({
2381 + onLongPress: createEventHandler('onLongPress'),
2382 + onLongPressChange: createEventHandler('onLongPressChange'),
2383 + onPress: createEventHandler('onPress'),
2384 + onPressChange: createEventHandler('onPressChange'),
2385 + onPressMove: createEventHandler('onPressMove'),
2386 + onPressStart: createEventHandler('onPressStart'),
2387 + onPressEnd: createEventHandler('onPressEnd'),
2388 + });
2389 + return (
2390 + <div
2391 + ref={ref}
2392 + responders={
2393 + <PressResponder
2394 + delayPressStart={250}
2395 + delayPressEnd={250}
2396 + enableLongPress={true}
2397 + />
2398 + }
2399 + />
2400 + );
2401 + };
2402 + ReactDOM.render(<Component />, container);
2403 +
2404 ref.current.getBoundingClientRect = () => ({
2405 top: 0,
2406 left: 0,
@@ -2291,30 +2461,42 @@ describe('Event responder: Press', () => {
2461 events.push(msg);
2462 };
2463
2294 - const element = (
2295 - <Press
2296 - onPress={createEventHandler('outer: onPress')}
2297 - onPressChange={createEventHandler('outer: onPressChange')}
2298 - onPressStart={createEventHandler('outer: onPressStart')}
2299 - onPressEnd={createEventHandler('outer: onPressEnd')}>
2300 - <Press
2301 - onPress={createEventHandler('inner: onPress')}
2302 - onPressChange={createEventHandler('inner: onPressChange')}
2303 - onPressStart={createEventHandler('inner: onPressStart')}
2304 - onPressEnd={createEventHandler('inner: onPressEnd')}
2305 - stopPropagation={false}>
2306 - <div
2307 - ref={ref}
2308 - onPointerDown={createEventHandler('pointerdown')}
2309 - onPointerUp={createEventHandler('pointerup')}
2310 - onKeyDown={createEventHandler('keydown')}
2311 - onKeyUp={createEventHandler('keyup')}
2312 - />
2313 - </Press>
2314 - </Press>
2315 - );
2464 + const Inner = () => {
2465 + usePressListener({
2466 + onPress: createEventHandler('inner: onPress'),
2467 + onPressChange: createEventHandler('inner: onPressChange'),
2468 + onPressMove: createEventHandler('inner: onPressMove'),
2469 + onPressStart: createEventHandler('inner: onPressStart'),
2470 + onPressEnd: createEventHandler('inner: onPressEnd'),
2471 + });
2472 + return (
2473 + <div
2474 + ref={ref}
2475 + responders={<PressResponder stopPropagation={false} />}
2476 + onPointerDown={createEventHandler('pointerdown')}
2477 + onPointerUp={createEventHandler('pointerup')}
2478 + onKeyDown={createEventHandler('keydown')}
2479 + onKeyUp={createEventHandler('keyup')}
2480 + />
2481 + );
2482 + };
2483 +
2484 + const Outer = () => {
2485 + usePressListener({
2486 + onPress: createEventHandler('outer: onPress'),
2487 + onPressChange: createEventHandler('outer: onPressChange'),
2488 + onPressMove: createEventHandler('outer: onPressMove'),
2489 + onPressStart: createEventHandler('outer: onPressStart'),
2490 + onPressEnd: createEventHandler('outer: onPressEnd'),
2491 + });
2492 + return (
2493 + <div responders={<PressResponder />}>
2494 + <Inner />
2495 + </div>
2496 + );
2497 + };
2498 + ReactDOM.render(<Outer />, container);
2499
2317 - ReactDOM.render(element, container);
2500 ref.current.getBoundingClientRect = () => ({
2501 top: 0,
2502 left: 0,
@@ -2341,14 +2523,26 @@ describe('Event responder: Press', () => {
2523 it('for onPress', () => {
2524 const ref = React.createRef();
2525 const fn = jest.fn();
2344 - const element = (
2345 - <Press onPress={fn}>
2346 - <Press onPress={fn}>
2347 - <div ref={ref} />
2348 - </Press>
2349 - </Press>
2350 - );
2351 - ReactDOM.render(element, container);
2526 +
2527 + const Inner = () => {
2528 + usePressListener({
2529 + onPress: fn,
2530 + });
2531 + return <div ref={ref} responders={<PressResponder />} />;
2532 + };
2533 +
2534 + const Outer = () => {
2535 + usePressListener({
2536 + onPress: fn,
2537 + });
2538 + return (
2539 + <div responders={<PressResponder />}>
2540 + <Inner />
2541 + </div>
2542 + );
2543 + };
2544 + ReactDOM.render(<Outer />, container);
2545 +
2546 ref.current.getBoundingClientRect = () => ({
2547 top: 0,
2548 left: 0,
@@ -2366,14 +2560,30 @@ describe('Event responder: Press', () => {
2560 it('for onLongPress', () => {
2561 const ref = React.createRef();
2562 const fn = jest.fn();
2369 - const element = (
2370 - <Press onLongPress={fn}>
2371 - <Press onLongPress={fn}>
2372 - <div ref={ref} />
2373 - </Press>
2374 - </Press>
2375 - );
2376 - ReactDOM.render(element, container);
2563 +
2564 + const Inner = () => {
2565 + usePressListener({
2566 + onLongPress: fn,
2567 + });
2568 + return (
2569 + <div
2570 + ref={ref}
2571 + responders={<PressResponder enableLongPress={true} />}
2572 + />
2573 + );
2574 + };
2575 +
2576 + const Outer = () => {
2577 + usePressListener({
2578 + onLongPress: fn,
2579 + });
2580 + return (
2581 + <div responders={<PressResponder enableLongPress={true} />}>
2582 + <Inner />
2583 + </div>
2584 + );
2585 + };
2586 + ReactDOM.render(<Outer />, container);
2587
2588 ref.current.dispatchEvent(createEvent('pointerdown'));
2589 jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
@@ -2385,14 +2595,27 @@ describe('Event responder: Press', () => {
2595 const ref = React.createRef();
2596 const fn = jest.fn();
2597 const fn2 = jest.fn();
2388 - const element = (
2389 - <Press onPressStart={fn} onPressEnd={fn2}>
2390 - <Press onPressStart={fn} onPressEnd={fn2}>
2391 - <div ref={ref} />
2392 - </Press>
2393 - </Press>
2394 - );
2395 - ReactDOM.render(element, container);
2598 +
2599 + const Inner = () => {
2600 + usePressListener({
2601 + onPressStart: fn,
2602 + onPressEnd: fn2,
2603 + });
2604 + return <div ref={ref} responders={<PressResponder />} />;
2605 + };
2606 +
2607 + const Outer = () => {
2608 + usePressListener({
2609 + onPressStart: fn,
2610 + onPressEnd: fn2,
2611 + });
2612 + return (
2613 + <div responders={<PressResponder />}>
2614 + <Inner />
2615 + </div>
2616 + );
2617 + };
2618 + ReactDOM.render(<Outer />, container);
2619
2620 ref.current.dispatchEvent(createEvent('pointerdown'));
2621 expect(fn).toHaveBeenCalledTimes(1);
@@ -2405,14 +2628,25 @@ describe('Event responder: Press', () => {
2628 it('for onPressChange', () => {
2629 const ref = React.createRef();
2630 const fn = jest.fn();
2408 - const element = (
2409 - <Press onPressChange={fn}>
2410 - <Press onPressChange={fn}>
2411 - <div ref={ref} />
2412 - </Press>
2413 - </Press>
2414 - );
2415 - ReactDOM.render(element, container);
2631 +
2632 + const Inner = () => {
2633 + usePressListener({
2634 + onPressChange: fn,
2635 + });
2636 + return <div ref={ref} responders={<PressResponder />} />;
2637 + };
2638 +
2639 + const Outer = () => {
2640 + usePressListener({
2641 + onPressChange: fn,
2642 + });
2643 + return (
2644 + <div responders={<PressResponder />}>
2645 + <Inner />
2646 + </div>
2647 + );
2648 + };
2649 + ReactDOM.render(<Outer />, container);
2650
2651 ref.current.dispatchEvent(createEvent('pointerdown'));
2652 expect(fn).toHaveBeenCalledTimes(1);
@@ -2427,12 +2661,14 @@ describe('Event responder: Press', () => {
2661 const onPress = jest.fn();
2662 const preventDefault = jest.fn();
2663 const ref = React.createRef();
2430 - const element = (
2431 - <Press onPress={onPress}>
2432 - <a href="#" ref={ref} />
2433 - </Press>
2434 - );
2435 - ReactDOM.render(element, container);
2664 +
2665 + const Component = () => {
2666 + usePressListener({
2667 + onPress,
2668 + });
2669 + return <a href="#" ref={ref} responders={<PressResponder />} />;
2670 + };
2671 + ReactDOM.render(<Component />, container);
2672
2673 ref.current.dispatchEvent(createEvent('pointerdown'));
2674 ref.current.dispatchEvent(
@@ -2452,12 +2688,14 @@ describe('Event responder: Press', () => {
2688 const onPress = jest.fn();
2689 const preventDefault = jest.fn();
2690 const ref = React.createRef();
2455 - const element = (
2456 - <Press onPress={onPress}>
2457 - <a href="#" ref={ref} />
2458 - </Press>
2459 - );
2460 - ReactDOM.render(element, container);
2691 +
2692 + const Component = () => {
2693 + usePressListener({
2694 + onPress,
2695 + });
2696 + return <a href="#" ref={ref} responders={<PressResponder />} />;
2697 + };
2698 + ReactDOM.render(<Component />, container);
2699
2700 ref.current.dispatchEvent(createEvent('keydown', {key: 'Enter'}));
2701 ref.current.dispatchEvent(createEvent('click', {preventDefault}));
@@ -2472,14 +2710,18 @@ describe('Event responder: Press', () => {
2710 const onPress = jest.fn();
2711 const preventDefault = jest.fn();
2712 const buttonRef = React.createRef();
2475 - const element = (
2476 - <a href="#">
2477 - <Press onPress={onPress}>
2478 - <button ref={buttonRef} />
2479 - </Press>
2480 - </a>
2481 - );
2482 - ReactDOM.render(element, container);
2713 +
2714 + const Component = () => {
2715 + usePressListener({
2716 + onPress,
2717 + });
2718 + return (
2719 + <a href="#">
2720 + <button ref={buttonRef} responders={<PressResponder />} />
2721 + </a>
2722 + );
2723 + };
2724 + ReactDOM.render(<Component />, container);
2725
2726 buttonRef.current.dispatchEvent(createEvent('pointerdown'));
2727 buttonRef.current.dispatchEvent(
@@ -2496,14 +2738,18 @@ describe('Event responder: Press', () => {
2738 const onPress = jest.fn();
2739 const preventDefault = jest.fn();
2740 const ref = React.createRef();
2499 - const element = (
2500 - <Press onPress={onPress}>
2501 - <a href="#">
2741 +
2742 + const Component = () => {
2743 + usePressListener({
2744 + onPress,
2745 + });
2746 + return (
2747 + <a href="#" responders={<PressResponder />}>
2748 <div ref={ref} />
2749 </a>
2504 - </Press>
2505 - );
2506 - ReactDOM.render(element, container);
2750 + );
2751 + };
2752 + ReactDOM.render(<Component />, container);
2753
2754 ref.current.dispatchEvent(createEvent('pointerdown'));
2755 ref.current.dispatchEvent(
@@ -2523,12 +2769,14 @@ describe('Event responder: Press', () => {
2769 const onPress = jest.fn();
2770 const preventDefault = jest.fn();
2771 const ref = React.createRef();
2526 - const element = (
2527 - <Press onPress={onPress}>
2528 - <a href="#" ref={ref} />
2529 - </Press>
2530 - );
2531 - ReactDOM.render(element, container);
2772 +
2773 + const Component = () => {
2774 + usePressListener({
2775 + onPress,
2776 + });
2777 + return <a href="#" ref={ref} responders={<PressResponder />} />;
2778 + };
2779 + ReactDOM.render(<Component />, container);
2780
2781 ['metaKey', 'ctrlKey', 'shiftKey'].forEach(modifierKey => {
2782 ref.current.dispatchEvent(
@@ -2555,12 +2803,20 @@ describe('Event responder: Press', () => {
2803 const onPress = jest.fn();
2804 const preventDefault = jest.fn();
2805 const ref = React.createRef();
2558 - const element = (
2559 - <Press onPress={onPress} preventDefault={false}>
2560 - <a href="#" ref={ref} />
2561 - </Press>
2562 - );
2563 - ReactDOM.render(element, container);
2806 +
2807 + const Component = () => {
2808 + usePressListener({
2809 + onPress,
2810 + });
2811 + return (
2812 + <a
2813 + href="#"
2814 + ref={ref}
2815 + responders={<PressResponder preventDefault={false} />}
2816 + />
2817 + );
2818 + };
2819 + ReactDOM.render(<Component />, container);
2820
2821 ref.current.dispatchEvent(createEvent('pointerdown'));
2822 ref.current.dispatchEvent(
@@ -2580,44 +2836,27 @@ describe('Event responder: Press', () => {
2836 const onPress = jest.fn();
2837 const preventDefault = jest.fn();
2838 const ref = React.createRef();
2583 - const element = (
2584 - <Press onPress={onPress} preventDefault={false}>
2585 - <a href="#" ref={ref} />
2586 - </Press>
2587 - );
2588 - ReactDOM.render(element, container);
2839
2590 - ref.current.dispatchEvent(createEvent('keydown', {key: 'Enter'}));
2591 - ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2592 - ref.current.dispatchEvent(createEvent('keyup', {key: 'Enter'}));
2593 - expect(preventDefault).not.toBeCalled();
2594 - expect(onPress).toHaveBeenCalledWith(
2595 - expect.objectContaining({defaultPrevented: false}),
2596 - );
2597 - });
2598 -
2599 - it('warns when preventDefault is used in an event hook', () => {
2600 - const onPress = jest.fn();
2601 - const preventDefault = jest.fn();
2602 - const ref = React.createRef();
2840 const Component = () => {
2604 - React.unstable_useEvent(Press, {preventDefault: false});
2605 -
2841 + usePressListener({
2842 + onPress,
2843 + });
2844 return (
2607 - <Press onPress={onPress}>
2608 - <a href="#" ref={ref} />
2609 - </Press>
2845 + <a
2846 + href="#"
2847 + ref={ref}
2848 + responders={<PressResponder preventDefault={false} />}
2849 + />
2850 );
2851 };
2852 ReactDOM.render(<Component />, container);
2853
2614 - expect(() => {
2615 - ref.current.dispatchEvent(createEvent('pointerdown'));
2616 - ref.current.dispatchEvent(createEvent('pointerup'));
2617 - ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2618 - }).toWarnDev(
2619 - '"preventDefault" prop cannot be passed to Press event hooks. This will result in a no-op.',
2620 - {withoutStack: true},
2854 + ref.current.dispatchEvent(createEvent('keydown', {key: 'Enter'}));
2855 + ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2856 + ref.current.dispatchEvent(createEvent('keyup', {key: 'Enter'}));
2857 + expect(preventDefault).not.toBeCalled();
2858 + expect(onPress).toHaveBeenCalledWith(
2859 + expect.objectContaining({defaultPrevented: false}),
2860 );
2861 });
2862 });
@@ -2627,12 +2866,21 @@ describe('Event responder: Press', () => {
2866 const onLongPress = jest.fn();
2867 const onPressEnd = jest.fn();
2868 const ref = React.createRef();
2630 - const element = (
2631 - <Press onLongPress={onLongPress} onPressEnd={onPressEnd}>
2632 - <a href="#" ref={ref} />
2633 - </Press>
2634 - );
2635 - ReactDOM.render(element, container);
2869 +
2870 + const Component = () => {
2871 + usePressListener({
2872 + onLongPress,
2873 + onPressEnd,
2874 + });
2875 + return (
2876 + <a
2877 + href="#"
2878 + ref={ref}
2879 + responders={<PressResponder enableLongPress={true} />}
2880 + />
2881 + );
2882 + };
2883 + ReactDOM.render(<Component />, container);
2884
2885 // Should cancel for non-mouse events
2886 ref.current.dispatchEvent(
@@ -2707,14 +2955,14 @@ describe('Event responder: Press', () => {
2955 it('does end on "scroll" to document', () => {
2956 const onPressEnd = jest.fn();
2957 const ref = React.createRef();
2710 - const element = (
2711 - <div>
2712 - <Press onPressEnd={onPressEnd}>
2713 - <a href="#" ref={ref} />
2714 - </Press>
2715 - </div>
2716 - );
2717 - ReactDOM.render(element, container);
2958 +
2959 + const Component = () => {
2960 + usePressListener({
2961 + onPressEnd,
2962 + });
2963 + return <a href="#" ref={ref} responders={<PressResponder />} />;
2964 + };
2965 + ReactDOM.render(<Component />, container);
2966
2967 ref.current.dispatchEvent(createEvent('pointerdown'));
2968 document.dispatchEvent(createEvent('scroll'));
@@ -2725,14 +2973,18 @@ describe('Event responder: Press', () => {
2973 const onPressEnd = jest.fn();
2974 const ref = React.createRef();
2975 const containerRef = React.createRef();
2728 - const element = (
2729 - <div ref={containerRef}>
2730 - <Press onPressEnd={onPressEnd}>
2731 - <a href="#" ref={ref} />
2732 - </Press>
2733 - </div>
2734 - );
2735 - ReactDOM.render(element, container);
2976 +
2977 + const Component = () => {
2978 + usePressListener({
2979 + onPressEnd,
2980 + });
2981 + return (
2982 + <div ref={containerRef}>
2983 + <a ref={ref} responders={<PressResponder />} />
2984 + </div>
2985 + );
2986 + };
2987 + ReactDOM.render(<Component />, container);
2988
2989 ref.current.dispatchEvent(createEvent('pointerdown'));
2990 containerRef.current.dispatchEvent(createEvent('scroll'));
@@ -2743,15 +2995,19 @@ describe('Event responder: Press', () => {
2995 const onPressEnd = jest.fn();
2996 const ref = React.createRef();
2997 const outsideRef = React.createRef();
2746 - const element = (
2747 - <div>
2748 - <Press onPressEnd={onPressEnd}>
2749 - <a href="#" ref={ref} />
2750 - </Press>
2751 - <span ref={outsideRef} />
2752 - </div>
2753 - );
2754 - ReactDOM.render(element, container);
2998 +
2999 + const Component = () => {
3000 + usePressListener({
3001 + onPressEnd,
3002 + });
3003 + return (
3004 + <div>
3005 + <a ref={ref} responders={<PressResponder />} />
3006 + <span ref={outsideRef} />
3007 + </div>
3008 + );
3009 + };
3010 + ReactDOM.render(<Component />, container);
3011
3012 ref.current.dispatchEvent(createEvent('pointerdown'));
3013 outsideRef.current.dispatchEvent(createEvent('scroll'));
@@ -2759,17 +3015,16 @@ describe('Event responder: Press', () => {
3015 });
3016
3017 it('expect displayName to show up for event component', () => {
2762 - expect(Press.responder.displayName).toBe('Press');
3018 + expect(PressResponder.displayName).toBe('Press');
3019 });
3020
3021 it('should not trigger an invariant in addRootEventTypes()', () => {
3022 const ref = React.createRef();
2767 - const element = (
2768 - <Press>
2769 - <button ref={ref} />
2770 - </Press>
2771 - );
2772 - ReactDOM.render(element, container);
3023 +
3024 + const Component = () => {
3025 + return <button ref={ref} responders={<PressResponder />} />;
3026 + };
3027 + ReactDOM.render(<Component />, container);
3028
3029 ref.current.dispatchEvent(createEvent('pointerdown'));
3030 jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
@@ -2798,17 +3053,23 @@ describe('Event responder: Press', () => {
3053 timeStamps.push(event.timeStamp);
3054 eventLog.push(propertiesWeCareAbout);
3055 };
2801 - const element = (
2802 - <Press
2803 - onPressStart={logEvent}
2804 - onPressEnd={logEvent}
2805 - onPressMove={logEvent}
2806 - onLongPress={logEvent}
2807 - onPress={logEvent}>
2808 - <button ref={ref} />
2809 - </Press>
2810 - );
2811 - ReactDOM.render(element, container);
3056 +
3057 + const Component = () => {
3058 + usePressListener({
3059 + onPressStart: logEvent,
3060 + onPressEnd: logEvent,
3061 + onPressMove: logEvent,
3062 + onLongPress: logEvent,
3063 + onPress: logEvent,
3064 + });
3065 + return (
3066 + <button
3067 + ref={ref}
3068 + responders={<PressResponder enableLongPress={true} />}
3069 + />
3070 + );
3071 + };
3072 + ReactDOM.render(<Component />, container);
3073
3074 ref.current.getBoundingClientRect = () => ({
3075 top: 10,
@@ -2962,17 +3223,20 @@ describe('Event responder: Press', () => {
3223 updateCounter(count => count + 1);
3224 }
3225
3226 + usePressListener({
3227 + onPress: handlePress,
3228 + });
3229 +
3230 return (
3231 <div>
2967 - <Press onPress={handlePress}>
2968 - <button
2969 - ref={ref}
2970 - onClick={() => {
2971 - updateCounter(count => count + 1);
2972 - }}>
2973 - Press me
2974 - </button>
2975 - </Press>
3232 + <button
3233 + ref={ref}
3234 + responders={<PressResponder />}
3235 + onClick={() => {
3236 + updateCounter(count => count + 1);
3237 + }}>
3238 + Press me
3239 + </button>
3240 </div>
3241 );
3242 }
@@ -3026,19 +3290,22 @@ describe('Event responder: Press', () => {
3290 updateCounter(count => count + 1);
3291 }
3292
3293 + usePressListener({
3294 + onPress: handlePress,
3295 + });
3296 +
3297 return (
3298 <div>
3031 - <Press onPress={handlePress}>
3032 - <button
3033 - ref={ref}
3034 - onClick={() => {
3035 - // This should flush synchronously
3036 - ReactDOM.unstable_flushDiscreteUpdates();
3037 - updateCounter(count => count + 1);
3038 - }}>
3039 - Press me
3040 - </button>
3041 - </Press>
3299 + <button
3300 + ref={ref}
3301 + responders={<PressResponder />}
3302 + onClick={() => {
3303 + // This should flush synchronously
3304 + ReactDOM.unstable_flushDiscreteUpdates();
3305 + updateCounter(count => count + 1);
3306 + }}>
3307 + Press me
3308 + </button>
3309 </div>
3310 );
3311 }
@@ -3104,15 +3371,18 @@ describe('Event responder: Press', () => {
3371 updatePressesCount(pressesCount + 1);
3372 }
3373
3374 + usePressListener({
3375 + onPress: handlePress,
3376 + });
3377 +
3378 return (
3379 <div>
3109 - <Press onPress={handlePress}>
3110 - <button
3111 - ref={button}
3112 - onClick={() => updateClicksCount(clicksCount + 1)}>
3113 - Presses: {pressesCount}, Clicks: {clicksCount}
3114 - </button>
3115 - </Press>
3380 + <button
3381 + responders={<PressResponder />}
3382 + ref={button}
3383 + onClick={() => updateClicksCount(clicksCount + 1)}>
3384 + Presses: {pressesCount}, Clicks: {clicksCount}
3385 + </button>
3386 </div>
3387 );
3388 }
@@ -3139,12 +3409,12 @@ describe('Event responder: Press', () => {
3409 it('is called after a right mouse click', () => {
3410 const onContextMenu = jest.fn();
3411 const ref = React.createRef();
3142 - const element = (
3143 - <Press onContextMenu={onContextMenu}>
3144 - <div ref={ref} />
3145 - </Press>
3146 - );
3147 - ReactDOM.render(element, container);
3412 + const Component = () => {
3413 + usePressListener({onContextMenu});
3414 +
3415 + return <div ref={ref} responders={<PressResponder />} />;
3416 + };
3417 + ReactDOM.render(<Component />, container);
3418
3419 ref.current.dispatchEvent(
3420 createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
@@ -3164,12 +3434,13 @@ describe('Event responder: Press', () => {
3434
3435 const onContextMenu = jest.fn();
3436 const ref = React.createRef();
3167 - const element = (
3168 - <Press onContextMenu={onContextMenu}>
3169 - <div ref={ref} />
3170 - </Press>
3171 - );
3172 - ReactDOM.render(element, container);
3437 +
3438 + const Component = () => {
3439 + usePressListener({onContextMenu});
3440 +
3441 + return <div ref={ref} responders={<PressResponder />} />;
3442 + };
3443 + ReactDOM.render(<Component />, container);
3444
3445 ref.current.dispatchEvent(
3446 createEvent('pointerdown', {
@@ -3194,12 +3465,13 @@ describe('Event responder: Press', () => {
3465
3466 const onContextMenu = jest.fn();
3467 const ref = React.createRef();
3197 - const element = (
3198 - <Press onContextMenu={onContextMenu}>
3199 - <div ref={ref} />
3200 - </Press>
3201 - );
3202 - ReactDOM.render(element, container);
3468 +
3469 + const Component = () => {
3470 + usePressListener({onContextMenu});
3471 +
3472 + return <div ref={ref} responders={<PressResponder />} />;
3473 + };
3474 + ReactDOM.render(<Component />, container);
3475
3476 ref.current.dispatchEvent(
3477 createEvent('pointerdown', {
@@ -3216,12 +3488,13 @@ describe('Event responder: Press', () => {
3488 it('is not called after a right mouse click occurs during an active press', () => {
3489 const onContextMenu = jest.fn();
3490 const ref = React.createRef();
3219 - const element = (
3220 - <Press onContextMenu={onContextMenu}>
3221 - <div ref={ref} />
3222 - </Press>
3223 - );
3224 - ReactDOM.render(element, container);
3491 +
3492 + const Component = () => {
3493 + usePressListener({onContextMenu});
3494 +
3495 + return <div ref={ref} responders={<PressResponder />} />;
3496 + };
3497 + ReactDOM.render(<Component />, container);
3498
3499 ref.current.dispatchEvent(
3500 createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
@@ -3233,12 +3506,19 @@ describe('Event responder: Press', () => {
3506 it('is still called if "preventContextMenu" is true', () => {
3507 const onContextMenu = jest.fn();
3508 const ref = React.createRef();
3236 - const element = (
3237 - <Press onContextMenu={onContextMenu} preventContextMenu={true}>
3238 - <div ref={ref} />
3239 - </Press>
3240 - );
3241 - ReactDOM.render(element, container);
3509 +
3510 + const Component = () => {
3511 + usePressListener({onContextMenu});
3512 +
3513 + return (
3514 + <div
3515 + ref={ref}
3516 + responders={<PressResponder preventContextMenu={true} />}
3517 + />
3518 + );
3519 + };
3520 + ReactDOM.render(<Component />, container);
3521 +
3522 ref.current.dispatchEvent(
3523 createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
3524 );
@@ -3250,40 +3530,18 @@ describe('Event responder: Press', () => {
3530 });
3531 });
3532
3253 - it('warns when preventContextMenu is used in an event hook', () => {
3533 + it('should work correctly with stopPropagation set to true', () => {
3534 const ref = React.createRef();
3255 - const Component = () => {
3256 - React.unstable_useEvent(Press, {preventContextMenu: false});
3535 + const pointerDownEvent = jest.fn();
3536
3537 + const Component = () => {
3538 return (
3259 - <Press preventContextMenu={true}>
3260 - <div ref={ref} />
3261 - </Press>
3539 + <div ref={ref} responders={<PressResponder stopPropagation={true} />} />
3540 );
3541 };
3264 - ReactDOM.render(<Component />, container);
3542
3266 - expect(() => {
3267 - ref.current.dispatchEvent(
3268 - createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
3269 - );
3270 - ref.current.dispatchEvent(createEvent('contextmenu'));
3271 - }).toWarnDev(
3272 - '"preventContextMenu" prop cannot be passed to Press event hooks. This will result in a no-op.',
3273 - {withoutStack: true},
3274 - );
3275 - });
3276 -
3277 - it('should work correctly with stopPropagation set to true', () => {
3278 - const ref = React.createRef();
3279 - const element = (
3280 - <Press stopPropagation={true}>
3281 - <div ref={ref} />
3282 - </Press>
3283 - );
3284 - const pointerDownEvent = jest.fn();
3543 container.addEventListener('pointerdown', pointerDownEvent);
3286 - ReactDOM.render(element, container);
3544 + ReactDOM.render(<Component />, container);
3545
3546 ref.current.dispatchEvent(
3547 createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
@@ -3296,13 +3554,11 @@ describe('Event responder: Press', () => {
3554 const ref = React.createRef();
3555 const onPress = jest.fn();
3556 const Component = () => {
3299 - React.unstable_useEvent(Press, {onPress});
3557 + usePressListener({onPress});
3558
3559 return (
3560 <div>
3303 - <Press>
3304 - <a href="#" ref={ref} />
3305 - </Press>
3561 + <a href="#" ref={ref} responders={<PressResponder />} />
3562 </div>
3563 );
3564 };
@@ -3319,31 +3575,4 @@ describe('Event responder: Press', () => {
3575 expect.objectContaining({target: ref.current}),
3576 );
3577 });
3322 -
3323 - it('warns when stopPropagation is used in an event hook', () => {
3324 - const ref = React.createRef();
3325 - const Component = () => {
3326 - React.unstable_useEvent(Press, {stopPropagation: false});
3327 -
3328 - return (
3329 - <Press stopPropagation={true}>
3330 - <a href="#" ref={ref} />
3331 - </Press>
3332 - );
3333 - };
3334 - const pointerDownEvent = jest.fn();
3335 - container.addEventListener('pointerdown', pointerDownEvent);
3336 - ReactDOM.render(<Component />, container);
3337 -
3338 - expect(() => {
3339 - ref.current.dispatchEvent(
3340 - createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
3341 - );
3342 - }).toWarnDev(
3343 - '"stopPropagation" prop cannot be passed to Press event hooks. This will result in a no-op.',
3344 - {withoutStack: true},
3345 - );
3346 - container.removeEventListener('pointerdown', pointerDownEvent);
3347 - expect(pointerDownEvent).toHaveBeenCalledTimes(0);
3348 - });
3578 });
packages/react-events/src/dom/__tests__/Scroll-test.internal.js
+34 -26
@@ -12,7 +12,8 @@
12 let React;
13 let ReactFeatureFlags;
14 let ReactDOM;
15 -let Scroll;
15 +let ScrollResponder;
16 +let useScrollListener;
17
18 const createEvent = (type, data) => {
19 const event = document.createEvent('CustomEvent');
@@ -34,7 +35,8 @@ describe('Scroll event responder', () => {
35 ReactFeatureFlags.enableFlareAPI = true;
36 React = require('react');
37 ReactDOM = require('react-dom');
37 - Scroll = require('react-events/scroll').Scroll;
38 + ScrollResponder = require('react-events/scroll').ScrollResponder;
39 + useScrollListener = require('react-events/scroll').useScrollListener;
40
41 container = document.createElement('div');
42 document.body.appendChild(container);
@@ -52,12 +54,15 @@ describe('Scroll event responder', () => {
54 beforeEach(() => {
55 onScroll = jest.fn();
56 ref = React.createRef();
55 - const element = (
56 - <Scroll disabled={true} onScroll={onScroll}>
57 - <div ref={ref} />
58 - </Scroll>
59 - );
60 - ReactDOM.render(element, container);
57 + const Component = () => {
58 + useScrollListener({
59 + onScroll,
60 + });
61 + return (
62 + <div ref={ref} responders={<ScrollResponder disabled={true} />} />
63 + );
64 + };
65 + ReactDOM.render(<Component />, container);
66 });
67
68 it('prevents custom events being dispatched', () => {
@@ -72,12 +77,13 @@ describe('Scroll event responder', () => {
77 beforeEach(() => {
78 onScroll = jest.fn();
79 ref = React.createRef();
75 - const element = (
76 - <Scroll onScroll={onScroll}>
77 - <div ref={ref} />
78 - </Scroll>
79 - );
80 - ReactDOM.render(element, container);
80 + const Component = () => {
81 + useScrollListener({
82 + onScroll,
83 + });
84 + return <div ref={ref} responders={<ScrollResponder />} />;
85 + };
86 + ReactDOM.render(<Component />, container);
87 });
88
89 describe('is called after "scroll" event', () => {
@@ -222,12 +228,13 @@ describe('Scroll event responder', () => {
228 beforeEach(() => {
229 onScrollDragStart = jest.fn();
230 ref = React.createRef();
225 - const element = (
226 - <Scroll onScrollDragStart={onScrollDragStart}>
227 - <div ref={ref} />
228 - </Scroll>
229 - );
230 - ReactDOM.render(element, container);
231 + const Component = () => {
232 + useScrollListener({
233 + onScrollDragStart,
234 + });
235 + return <div ref={ref} responders={<ScrollResponder />} />;
236 + };
237 + ReactDOM.render(<Component />, container);
238 });
239
240 it('works as expected with touch events', () => {
@@ -255,12 +262,13 @@ describe('Scroll event responder', () => {
262 beforeEach(() => {
263 onScrollDragEnd = jest.fn();
264 ref = React.createRef();
258 - const element = (
259 - <Scroll onScrollDragEnd={onScrollDragEnd}>
260 - <div ref={ref} />
261 - </Scroll>
262 - );
263 - ReactDOM.render(element, container);
265 + const Component = () => {
266 + useScrollListener({
267 + onScrollDragEnd,
268 + });
269 + return <div ref={ref} responders={<ScrollResponder />} />;
270 + };
271 + ReactDOM.render(<Component />, container);
272 });
273
274 it('works as expected with touch events', () => {
packages/react-events/src/rn/Press.js
+61 -97
@@ -15,38 +15,35 @@ import type {
15 ReactFaricEventTouch,
16 EventPriority,
17 } from 'react-native-renderer/src/ReactNativeTypes';
18 -import type {ReactEventResponder} from 'shared/ReactTypes';
18 import React from 'react';
19 import {
20 DiscreteEvent,
21 UserBlockingEvent,
22 } from 'react-native-renderer/src/ReactNativeTypes';
23
25 -type ReactNativeEventResponder = ReactEventResponder<
26 - ReactNativeResponderEvent,
27 - ReactNativeResponderContext,
28 ->;
29 -
30 -type PressProps = {
31 - disabled: boolean,
32 - delayLongPress: number,
33 - delayPressEnd: number,
34 - delayPressStart: number,
35 - onContextMenu: (e: PressEvent) => void,
24 +type PressListenerProps = {|
25 onLongPress: (e: PressEvent) => void,
26 onLongPressChange: boolean => void,
38 - onLongPressShouldCancelPress: () => boolean,
27 onPress: (e: PressEvent) => void,
28 onPressChange: boolean => void,
29 onPressEnd: (e: PressEvent) => void,
30 onPressMove: (e: PressEvent) => void,
31 onPressStart: (e: PressEvent) => void,
32 +|};
33 +
34 +type PressProps = {
35 + disabled: boolean,
36 + delayLongPress: number,
37 + delayPressEnd: number,
38 + delayPressStart: number,
39 pressRetentionOffset: {
40 top: number,
41 right: number,
42 bottom: number,
43 left: number,
44 },
45 + enableLongPress: boolean,
46 + longPressShouldCancelPress: () => boolean,
47 };
48
49 type PressEvent = {|
@@ -166,11 +163,11 @@ function createPressEvent(
163 }
164
165 function dispatchEvent(
166 + eventPropName: string,
167 event: ?ReactNativeResponderEvent,
168 context: ReactNativeResponderContext,
169 state: PressState,
170 name: PressEventType,
173 - listener: (e: Object) => void,
171 eventPriority: EventPriority,
172 ): void {
173 const target = ((state.pressTarget: any): ReactNativeEventTarget);
@@ -182,7 +179,7 @@ function dispatchEvent(
179 pointerType,
180 state.touchEvent,
181 );
185 - context.dispatchEvent(syntheticEvent, listener, eventPriority);
182 + context.dispatchEvent(eventPropName, syntheticEvent, eventPriority);
183 }
184
185 function dispatchCancel(event, context, props, state): void {
@@ -270,27 +267,14 @@ function calculateResponderRegion(
267 });
268 }
269
273 -function dispatchPressChangeEvent(event, context, props, state): void {
270 +function dispatchPressChangeEvent(context, state): void {
271 const bool = state.isActivePressed;
275 - const listener = () => {
276 - props.onPressChange(bool);
277 - };
278 - dispatchEvent(event, context, state, 'presschange', listener, DiscreteEvent);
272 + context.dispatchEvent('onPressChange', bool, DiscreteEvent);
273 }
274
281 -function dispatchLongPressChangeEvent(event, context, props, state): void {
275 +function dispatchLongPressChangeEvent(context, state): void {
276 const bool = state.isLongPressed;
283 - const listener = () => {
284 - props.onLongPressChange(bool);
285 - };
286 - dispatchEvent(
287 - event,
288 - context,
289 - state,
290 - 'longpresschange',
291 - listener,
292 - DiscreteEvent,
293 - );
277 + context.dispatchEvent('onLongPressChange', bool, DiscreteEvent);
278 }
279
280 function activate(event: ReactNativeResponderEvent, context, props, state) {
@@ -301,18 +285,16 @@ function activate(event: ReactNativeResponderEvent, context, props, state) {
285 state.activationPosition = {x, y};
286 }
287
304 - if (props.onPressStart) {
305 - dispatchEvent(
306 - event,
307 - context,
308 - state,
309 - 'pressstart',
310 - props.onPressStart,
311 - DiscreteEvent,
312 - );
313 - }
314 - if (!wasActivePressed && props.onPressChange) {
315 - dispatchPressChangeEvent(event, context, props, state);
288 + dispatchEvent(
289 + 'onPressStart',
290 + event,
291 + context,
292 + state,
293 + 'pressstart',
294 + DiscreteEvent,
295 + );
296 + if (!wasActivePressed) {
297 + dispatchPressChangeEvent(context, state);
298 }
299 }
300
@@ -321,21 +303,10 @@ function deactivate(event, context, props, state) {
303 state.isActivePressed = false;
304 state.isLongPressed = false;
305
324 - if (props.onPressEnd) {
325 - dispatchEvent(
326 - event,
327 - context,
328 - state,
329 - 'pressend',
330 - props.onPressEnd,
331 - DiscreteEvent,
332 - );
333 - }
334 - if (props.onPressChange) {
335 - dispatchPressChangeEvent(event, context, props, state);
336 - }
337 - if (wasLongPressed && props.onLongPressChange) {
338 - dispatchLongPressChangeEvent(event, context, props, state);
306 + dispatchEvent('onPressEnd', event, context, state, 'pressend', DiscreteEvent);
307 + dispatchPressChangeEvent(context, state);
308 + if (wasLongPressed && props.enableLongPress) {
309 + dispatchLongPressChangeEvent(context, state);
310 }
311 }
312
@@ -408,10 +379,7 @@ function dispatchPressStartEvents(event, context, props, state) {
379 state.isActivePressStart = true;
380 activate(event, context, props, state);
381
411 - if (
412 - (props.onLongPress || props.onLongPressChange) &&
413 - !state.isLongPressed
414 - ) {
382 + if (!state.isLongPressed && props.enableLongPress) {
383 const delayLongPress = calculateDelayMS(
384 props.delayLongPress,
385 10,
@@ -420,19 +388,15 @@ function dispatchPressStartEvents(event, context, props, state) {
388 state.longPressTimeout = context.setTimeout(() => {
389 state.isLongPressed = true;
390 state.longPressTimeout = null;
423 - if (props.onLongPress) {
424 - dispatchEvent(
425 - event,
426 - context,
427 - state,
428 - 'longpress',
429 - props.onLongPress,
430 - DiscreteEvent,
431 - );
432 - }
433 - if (props.onLongPressChange) {
434 - dispatchLongPressChangeEvent(event, context, props, state);
435 - }
391 + dispatchEvent(
392 + 'onLongPress',
393 + event,
394 + context,
395 + state,
396 + 'longpress',
397 + DiscreteEvent,
398 + );
399 + dispatchLongPressChangeEvent(context, state);
400 }, delayLongPress);
401 }
402 };
@@ -499,8 +463,7 @@ function dispatchPressEndEvents(event, context, props, state): void {
463 state.responderRegionOnDeactivation = null;
464 }
465
502 -const PressResponder: ReactNativeEventResponder = {
503 - displayName: 'Press',
466 +const pressResponderImpl = {
467 targetEventTypes,
468 getInitialState(): PressState {
469 return {
@@ -533,8 +496,7 @@ const PressResponder: ReactNativeEventResponder = {
496 if (type === 'topTouchStart') {
497 if (!state.isPressed) {
498 state.pointerType = 'touch';
536 - const pressTarget = (state.pressTarget = event.currentTarget);
537 -
499 + const pressTarget = (state.pressTarget = event.responderTarget);
500 const touchEvent = getTouchFromPressEvent(nativeEvent);
501 if (touchEvent === null) {
502 return;
@@ -583,16 +545,14 @@ const PressResponder: ReactNativeEventResponder = {
545 () => {
546 if (state.isPressWithinResponderRegion) {
547 if (state.isPressed) {
586 - if (props.onPressMove) {
587 - dispatchEvent(
588 - event,
589 - context,
590 - state,
591 - 'pressmove',
592 - props.onPressMove,
593 - UserBlockingEvent,
594 - );
595 - }
548 + dispatchEvent(
549 + 'onPressMove',
550 + event,
551 + context,
552 + state,
553 + 'pressmove',
554 + UserBlockingEvent,
555 + );
556 if (
557 state.activationPosition != null &&
558 state.longPressTimeout != null
@@ -626,7 +586,7 @@ const PressResponder: ReactNativeEventResponder = {
586 const wasLongPressed = state.isLongPressed;
587 dispatchPressEndEvents(event, context, props, state);
588
629 - if (state.pressTarget !== null && props.onPress) {
589 + if (state.pressTarget !== null) {
590 // If the event target isn't within the press target, check if we're still
591 // within the responder region. The region may have changed if the
592 // element's layout was modified after activation.
@@ -641,16 +601,17 @@ const PressResponder: ReactNativeEventResponder = {
601 if (
602 !(
603 wasLongPressed &&
644 - props.onLongPressShouldCancelPress &&
645 - props.onLongPressShouldCancelPress()
604 + props.enableLongPress &&
605 + props.longPressShouldCancelPress &&
606 + props.longPressShouldCancelPress()
607 )
608 ) {
609 dispatchEvent(
610 + 'onPress',
611 event,
612 context,
613 state,
614 'press',
653 - props.onPress,
615 DiscreteEvent,
616 );
617 }
@@ -670,8 +631,11 @@ const PressResponder: ReactNativeEventResponder = {
631 },
632 };
633
673 -export const Press = React.unstable_createEvent(PressResponder);
634 +export const PressResponder = React.unstable_createResponder(
635 + 'Press',
636 + pressResponderImpl,
637 +);
638
675 -export function usePress(props: PressProps): void {
676 - React.unstable_useEvent(Press, props);
639 +export function usePressListener(props: PressListenerProps): void {
640 + React.unstable_useListener(PressResponder, props);
641 }
packages/react-native-renderer/src/ReactFabricEventResponderSystem.js
+281 -318
@@ -8,9 +8,10 @@
8 */
9
10 import {
11 - EventComponent,
11 HostComponent,
12 FunctionComponent,
13 + MemoComponent,
14 + ForwardRef,
15 } from 'shared/ReactWorkTags';
16 import type {Fiber} from 'react-reconciler/src/ReactFiber';
17 import {
@@ -20,7 +21,7 @@ import {
21 } from 'events/ReactGenericBatching';
22 import type {
23 ReactEventResponder,
23 - ReactEventComponentInstance,
24 + ReactEventResponderInstance,
25 } from 'shared/ReactTypes';
26 import type {
27 ReactNativeResponderContext,
@@ -47,12 +48,11 @@ const {
48 unstable_runWithPriority: runWithPriority,
49 } = Scheduler;
50
50 -type EventObjectType = $Shape<PartialEventObject>;
51 -
52 -type PartialEventObject = {
53 - target: ReactNativeEventTarget,
54 - type: string,
55 -};
51 +type EventQueueItem = {|
52 + listeners: Array<(val: any) => void>,
53 + value: any,
54 +|};
55 +type EventQueue = Array<EventQueueItem>;
56
57 type ResponderTimeout = {|
58 id: TimeoutID,
@@ -60,24 +60,18 @@ type ResponderTimeout = {|
60 |};
61
62 type ResponderTimer = {|
63 - isHook: boolean,
64 - instance: ReactNativeEventComponentInstance,
63 + instance: ReactNativeEventResponderInstance,
64 func: () => void,
65 id: number,
66 timeStamp: number,
67 |};
68
70 -type EventQueue = {
71 - events: Array<EventObjectType>,
72 - eventPriority: EventPriority,
73 -};
74 -
69 type ReactNativeEventResponder = ReactEventResponder<
70 ReactNativeResponderEvent,
71 ReactNativeResponderContext,
72 >;
73
80 -type ReactNativeEventComponentInstance = ReactEventComponentInstance<
74 +type ReactNativeEventResponderInstance = ReactEventResponderInstance<
75 ReactNativeResponderEvent,
76 ReactNativeResponderContext,
77 >;
@@ -85,92 +79,43 @@ type ReactNativeEventComponentInstance = ReactEventComponentInstance<
79 const {measureInWindow} = nativeFabricUIManager;
80
81 const activeTimeouts: Map<number, ResponderTimeout> = new Map();
88 -const rootEventTypesToEventComponentInstances: Map<
82 +const rootEventTypesToEventResponderInstances: Map<
83 string,
90 - Set<ReactNativeEventComponentInstance>,
84 + Set<ReactNativeEventResponderInstance>,
85 > = new Map();
86 const ownershipChangeListeners: Set<
93 - ReactNativeEventComponentInstance,
87 + ReactNativeEventResponderInstance,
88 > = new Set();
95 -const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
96 -const eventListeners:
97 - | WeakMap
98 - | Map<
99 - $Shape<PartialEventObject>,
100 - ($Shape<PartialEventObject>) => void,
101 - > = new PossiblyWeakMap();
89
90 let globalOwner = null;
104 -let continueLocalPropagation = false;
91
92 let currentTimeStamp = 0;
93 let currentTimers = new Map();
108 -let currentInstance: null | ReactNativeEventComponentInstance = null;
94 +let currentInstance: null | ReactNativeEventResponderInstance = null;
95 let currentEventQueue: null | EventQueue = null;
96 +let currentEventQueuePriority: EventPriority = ContinuousEvent;
97 let currentTimerIDCounter = 0;
111 -let currentlyInHook = false;
98
99 const eventResponderContext: ReactNativeResponderContext = {
100 dispatchEvent(
115 - possibleEventObject: Object,
116 - listener: ($Shape<PartialEventObject>) => void,
101 + eventProp: string,
102 + eventValue: any,
103 eventPriority: EventPriority,
104 ): void {
105 validateResponderContext();
120 - const {target, type, timeStamp} = possibleEventObject;
121 -
122 - if (target == null || type == null || timeStamp == null) {
123 - throw new Error(
124 - 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
106 + validateEventValue(eventValue);
107 + if (eventPriority < currentEventQueuePriority) {
108 + currentEventQueuePriority = eventPriority;
109 + }
110 + const responderInstance = ((currentInstance: any): ReactNativeEventResponderInstance);
111 + const target = responderInstance.fiber;
112 + const responder = responderInstance.responder;
113 + const listeners = collectListeners(eventProp, responder, target);
114 + if (listeners.length !== 0) {
115 + ((currentEventQueue: any): EventQueue).push(
116 + createEventQueueItem(eventValue, listeners),
117 );
118 }
127 - const showWarning = name => {
128 - if (__DEV__) {
129 - warning(
130 - false,
131 - '%s is not available on event objects created from event responder modules (React Flare). ' +
132 - 'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.%s }`',
133 - name,
134 - name,
135 - );
136 - }
137 - };
138 - possibleEventObject.preventDefault = () => {
139 - if (__DEV__) {
140 - showWarning('preventDefault()');
141 - }
142 - };
143 - possibleEventObject.stopPropagation = () => {
144 - if (__DEV__) {
145 - showWarning('stopPropagation()');
146 - }
147 - };
148 - possibleEventObject.isDefaultPrevented = () => {
149 - if (__DEV__) {
150 - showWarning('isDefaultPrevented()');
151 - }
152 - };
153 - possibleEventObject.isPropagationStopped = () => {
154 - if (__DEV__) {
155 - showWarning('isPropagationStopped()');
156 - }
157 - };
158 - // $FlowFixMe: we don't need value, Flow thinks we do
159 - Object.defineProperty(possibleEventObject, 'nativeEvent', {
160 - get() {
161 - if (__DEV__) {
162 - showWarning('nativeEvent');
163 - }
164 - },
165 - });
166 -
167 - const eventObject = ((possibleEventObject: any): $Shape<
168 - PartialEventObject,
169 - >);
170 - const eventQueue = ((currentEventQueue: any): EventQueue);
171 - eventQueue.eventPriority = eventPriority;
172 - eventListeners.set(eventObject, listener);
173 - eventQueue.events.push(eventObject);
119 },
120 isTargetWithinNode(
121 childTarget: ReactNativeEventTarget,
@@ -211,7 +156,7 @@ const eventResponderContext: ReactNativeResponderContext = {
156 validateResponderContext();
157 for (let i = 0; i < rootEventTypes.length; i++) {
158 const rootEventType = rootEventTypes[i];
214 - const eventComponentInstance = ((currentInstance: any): ReactNativeEventComponentInstance);
159 + const eventComponentInstance = ((currentInstance: any): ReactNativeEventResponderInstance);
160 registerRootEventType(rootEventType, eventComponentInstance);
161 }
162 },
@@ -220,17 +165,17 @@ const eventResponderContext: ReactNativeResponderContext = {
165 for (let i = 0; i < rootEventTypes.length; i++) {
166 const rootEventType = rootEventTypes[i];
167
223 - let rootEventComponents = rootEventTypesToEventComponentInstances.get(
168 + let rootEventComponents = rootEventTypesToEventResponderInstances.get(
169 rootEventType,
170 );
226 - let rootEventTypesSet = ((currentInstance: any): ReactNativeEventComponentInstance)
171 + let rootEventTypesSet = ((currentInstance: any): ReactNativeEventResponderInstance)
172 .rootEventTypes;
173 if (rootEventTypesSet !== null) {
174 rootEventTypesSet.delete(rootEventType);
175 }
176 if (rootEventComponents !== undefined) {
177 rootEventComponents.delete(
233 - ((currentInstance: any): ReactNativeEventComponentInstance),
178 + ((currentInstance: any): ReactNativeEventResponderInstance),
179 );
180 }
181 }
@@ -255,8 +200,7 @@ const eventResponderContext: ReactNativeResponderContext = {
200 currentTimers.set(delay, timeout);
201 }
202 timeout.timers.set(timerId, {
258 - isHook: currentlyInHook,
259 - instance: ((currentInstance: any): ReactNativeEventComponentInstance),
203 + instance: ((currentInstance: any): ReactNativeEventResponderInstance),
204 func,
205 id: timerId,
206 timeStamp: currentTimeStamp,
@@ -282,6 +226,67 @@ const eventResponderContext: ReactNativeResponderContext = {
226 },
227 };
228
229 +function createEventQueueItem(
230 + value: any,
231 + listeners: Array<(val: any) => void>,
232 +): EventQueueItem {
233 + return {
234 + value,
235 + listeners,
236 + };
237 +}
238 +
239 +function validateEventValue(eventValue: any): void {
240 + if (typeof eventValue === 'object' && eventValue !== null) {
241 + const {target, type, timeStamp} = eventValue;
242 +
243 + if (target == null || type == null || timeStamp == null) {
244 + throw new Error(
245 + 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
246 + );
247 + }
248 + const showWarning = name => {
249 + if (__DEV__) {
250 + warning(
251 + false,
252 + '%s is not available on event objects created from event responder modules (React Flare). ' +
253 + 'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.%s }`',
254 + name,
255 + name,
256 + );
257 + }
258 + };
259 + eventValue.preventDefault = () => {
260 + if (__DEV__) {
261 + showWarning('preventDefault()');
262 + }
263 + };
264 + eventValue.stopPropagation = () => {
265 + if (__DEV__) {
266 + showWarning('stopPropagation()');
267 + }
268 + };
269 + eventValue.isDefaultPrevented = () => {
270 + if (__DEV__) {
271 + showWarning('isDefaultPrevented()');
272 + }
273 + };
274 + eventValue.isPropagationStopped = () => {
275 + if (__DEV__) {
276 + showWarning('isPropagationStopped()');
277 + }
278 + };
279 + // $FlowFixMe: we don't need value, Flow thinks we do
280 + Object.defineProperty(eventValue, 'nativeEvent', {
281 + get() {
282 + if (__DEV__) {
283 + showWarning('nativeEvent');
284 + }
285 + },
286 + });
287 + }
288 +}
289 +
290 function getFiberFromTarget(
291 target: null | ReactNativeEventTarget,
292 ): Fiber | null {
@@ -296,13 +301,13 @@ function processTimers(
301 delay: number,
302 ): void {
303 const timersArr = Array.from(timers.values());
299 - currentEventQueue = createEventQueue();
304 + currentEventQueuePriority = ContinuousEvent;
305 try {
306 for (let i = 0; i < timersArr.length; i++) {
302 - const {isHook, instance, func, id, timeStamp} = timersArr[i];
307 + const {instance, func, id, timeStamp} = timersArr[i];
308 currentInstance = instance;
309 + currentEventQueue = [];
310 currentTimeStamp = timeStamp + delay;
305 - currentlyInHook = isHook;
311 try {
312 func();
313 } finally {
@@ -324,8 +329,8 @@ function createFabricResponderEvent(
329 target: null | ReactNativeEventTarget,
330 ): ReactNativeResponderEvent {
331 return {
327 - currentTarget: target,
332 nativeEvent,
333 + responderTarget: target,
334 target,
335 type: topLevelType,
336 };
@@ -339,29 +344,18 @@ function validateResponderContext(): void {
344 );
345 }
346
342 -// TODO this function is almost an exact copy of the DOM version, we should
343 -// somehow share the logic
344 -function createEventQueue(): EventQueue {
345 - return {
346 - events: [],
347 - eventPriority: ContinuousEvent,
348 - };
349 -}
350 -
347 // TODO this function is almost an exact copy of the DOM version, we should
348 // somehow share the logic
349 function processEventQueue(): void {
354 - const {events, eventPriority} = ((currentEventQueue: any): EventQueue);
355 -
356 - if (events.length === 0) {
350 + const eventQueue = ((currentEventQueue: any): EventQueue);
351 + if (eventQueue.length === 0) {
352 return;
353 }
359 -
360 - switch (eventPriority) {
354 + switch (currentEventQueuePriority) {
355 case DiscreteEvent: {
356 flushDiscreteUpdatesIfNeeded(currentTimeStamp);
357 discreteUpdates(() => {
364 - batchedEventUpdates(processEvents, events);
358 + batchedEventUpdates(processEvents, eventQueue);
359 });
360 break;
361 }
@@ -369,15 +363,15 @@ function processEventQueue(): void {
363 if (enableUserBlockingEvents) {
364 runWithPriority(
365 UserBlockingPriority,
372 - batchedEventUpdates.bind(null, processEvents, events),
366 + batchedEventUpdates.bind(null, processEvents, eventQueue),
367 );
368 } else {
375 - batchedEventUpdates(processEvents, events);
369 + batchedEventUpdates(processEvents, eventQueue);
370 }
371 break;
372 }
373 case ContinuousEvent: {
380 - batchedEventUpdates(processEvents, events);
374 + batchedEventUpdates(processEvents, eventQueue);
375 break;
376 }
377 }
@@ -385,36 +379,10 @@ function processEventQueue(): void {
379
380 // TODO this function is almost an exact copy of the DOM version, we should
381 // somehow share the logic
388 -function triggerOwnershipListeners(): void {
389 - const listeningInstances = Array.from(ownershipChangeListeners);
390 - const previousInstance = currentInstance;
391 - const previouslyInHook = currentlyInHook;
392 - currentEventQueue = createEventQueue();
393 - try {
394 - for (let i = 0; i < listeningInstances.length; i++) {
395 - const instance = listeningInstances[i];
396 - const {isHook, props, responder, state} = instance;
397 - currentInstance = instance;
398 - currentlyInHook = isHook;
399 - const onOwnershipChange = ((responder: any): ReactNativeEventResponder)
400 - .onOwnershipChange;
401 - if (onOwnershipChange !== undefined) {
402 - onOwnershipChange(eventResponderContext, props, state);
403 - }
404 - }
405 - processEventQueue();
406 - } finally {
407 - currentInstance = previousInstance;
408 - currentlyInHook = previouslyInHook;
409 - }
410 -}
411 -
412 -// TODO this function is almost an exact copy of the DOM version, we should
413 -// somehow share the logic
414 -function releaseOwnershipForEventComponentInstance(
415 - eventComponentInstance: ReactNativeEventComponentInstance,
382 +function releaseOwnershipForEventResponderInstance(
383 + eventResponderInstance: ReactNativeEventResponderInstance,
384 ): boolean {
417 - if (globalOwner === eventComponentInstance) {
385 + if (globalOwner === eventResponderInstance) {
386 globalOwner = null;
387 triggerOwnershipListeners();
388 return true;
@@ -424,19 +392,72 @@ function releaseOwnershipForEventComponentInstance(
392
393 // TODO this function is almost an exact copy of the DOM version, we should
394 // somehow share the logic
427 -function processEvent(event: $Shape<PartialEventObject>): void {
428 - const type = event.type;
429 - const listener = ((eventListeners.get(event): any): (
430 - $Shape<PartialEventObject>,
431 - ) => void);
432 - invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
395 +function collectListeners(
396 + eventProp: string,
397 + eventResponder: ReactNativeEventResponder,
398 + target: Fiber,
399 +): Array<(any) => void> {
400 + const eventListeners = [];
401 + let node = target.return;
402 + nodeTraversal: while (node !== null) {
403 + switch (node.tag) {
404 + case HostComponent: {
405 + const dependencies = node.dependencies;
406 +
407 + if (dependencies !== null) {
408 + const respondersMap = dependencies.responders;
409 +
410 + if (respondersMap !== null && respondersMap.has(eventResponder)) {
411 + break nodeTraversal;
412 + }
413 + }
414 + break;
415 + }
416 + case FunctionComponent:
417 + case MemoComponent:
418 + case ForwardRef: {
419 + const dependencies = node.dependencies;
420 +
421 + if (dependencies !== null) {
422 + const listeners = dependencies.listeners;
423 +
424 + if (listeners !== null) {
425 + for (
426 + let s = 0, listenersLength = listeners.length;
427 + s < listenersLength;
428 + s++
429 + ) {
430 + const listener = listeners[s];
431 + const {responder, props} = listener;
432 + const listenerFunc = props[eventProp];
433 +
434 + if (
435 + responder === eventResponder &&
436 + typeof listenerFunc === 'function'
437 + ) {
438 + eventListeners.push(listenerFunc);
439 + }
440 + }
441 + }
442 + }
443 + }
444 + }
445 + node = node.return;
446 + }
447 + return eventListeners;
448 }
449
450 // TODO this function is almost an exact copy of the DOM version, we should
451 // somehow share the logic
437 -function processEvents(events: Array<EventObjectType>): void {
438 - for (let i = 0, length = events.length; i < length; i++) {
439 - processEvent(events[i]);
452 +function processEvents(eventQueue: EventQueue): void {
453 + for (let i = 0, length = eventQueue.length; i < length; i++) {
454 + const {value, listeners} = eventQueue[i];
455 + for (let s = 0, length2 = listeners.length; s < length2; s++) {
456 + const listener = listeners[s];
457 + const type =
458 + typeof value === 'object' && value !== null ? value.type : '';
459 + invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, value);
460 + }
461 }
462 }
463
@@ -454,172 +475,90 @@ function responderEventTypesContainType(
475 return false;
476 }
477
457 -// TODO this function is almost an exact copy of the DOM version, we should
458 -// somehow share the logic
459 -function shouldSkipEventComponent(
460 - eventResponderInstance: ReactNativeEventComponentInstance,
478 +function validateResponderTargetEventTypes(
479 + eventType: string,
480 responder: ReactNativeEventResponder,
462 - propagatedEventResponders: null | Set<ReactNativeEventResponder>,
463 - isHook: boolean,
481 ): boolean {
465 - if (propagatedEventResponders !== null && !isHook) {
466 - if (propagatedEventResponders.has(responder)) {
467 - return true;
468 - }
469 - propagatedEventResponders.add(responder);
470 - }
471 - if (globalOwner && globalOwner !== eventResponderInstance) {
472 - return true;
482 + const {targetEventTypes} = responder;
483 + // Validate the target event type exists on the responder
484 + if (targetEventTypes !== null) {
485 + return responderEventTypesContainType(targetEventTypes, eventType);
486 }
487 return false;
488 }
489
477 -function checkForLocalPropagationContinuation(
478 - responder: ReactNativeEventResponder,
479 - propagatedEventResponders: Set<ReactNativeEventResponder>,
480 -): void {
481 - if (continueLocalPropagation === true) {
482 - propagatedEventResponders.delete(responder);
483 - continueLocalPropagation = false;
484 - }
485 -}
486 -
487 -// TODO this function is almost an exact copy of the DOM version, we should
488 -// somehow share the logic
489 -function handleTargetEventResponderInstance(
490 - topLevelType: string,
491 - responderEvent: ReactNativeResponderEvent,
492 - eventComponentInstance: ReactNativeEventComponentInstance,
493 - hookComponentResponderValidation: null | Set<ReactNativeEventResponder>,
494 - propagatedEventResponders: null | Set<ReactNativeEventResponder>,
495 -): void {
496 - const responder = eventComponentInstance.responder;
497 - const targetEventTypes = responder.targetEventTypes;
498 - // Validate the target event type exists on the responder
499 - if (targetEventTypes !== undefined) {
500 - if (responderEventTypesContainType(targetEventTypes, topLevelType)) {
501 - if (hookComponentResponderValidation !== null) {
502 - hookComponentResponderValidation.add(responder);
503 - }
504 - const {isHook, props, state} = eventComponentInstance;
505 - const eventListener = responder.onEvent;
506 - if (eventListener !== undefined) {
507 - if (
508 - shouldSkipEventComponent(
509 - eventComponentInstance,
510 - ((responder: any): ReactNativeEventResponder),
511 - propagatedEventResponders,
512 - isHook,
513 - )
514 - ) {
515 - return;
516 - }
517 - currentInstance = eventComponentInstance;
518 - currentlyInHook = isHook;
519 - eventListener(responderEvent, eventResponderContext, props, state);
520 - if (!isHook) {
521 - checkForLocalPropagationContinuation(
522 - responder,
523 - ((propagatedEventResponders: any): Set<ReactNativeEventResponder>),
524 - );
525 - }
526 - }
527 - }
528 - }
490 +function validateOwnership(
491 + responderInstance: ReactNativeEventResponderInstance,
492 +): boolean {
493 + return globalOwner === null || globalOwner === responderInstance;
494 }
495
496 // TODO this function is almost an exact copy of the DOM version, we should
497 // somehow share the logic
498 function traverseAndHandleEventResponderInstances(
534 - topLevelType: string,
499 + eventType: string,
500 targetFiber: null | Fiber,
501 nativeEvent: ReactFaricEvent,
502 ): void {
503 // Trigger event responders in this order:
539 - // - Bubble target phase
540 - // - Root phase
504 + // - Bubble target responder phase
505 + // - Root responder phase
506
507 const responderEvent = createFabricResponderEvent(
543 - topLevelType,
508 + eventType,
509 nativeEvent,
510 targetFiber !== null
511 ? ((targetFiber.stateNode: any): ReactNativeEventTarget)
512 : null,
513 );
549 - const propagatedEventResponders: Set<ReactNativeEventResponder> = new Set();
550 -
551 - // We use this to know if we should check add hooks. If there are
552 - // no event targets, then we don't add the hook forms.
553 - const hookComponentResponderValidation = new Set();
554 -
555 - // Bubbled event phases have the notion of local propagation.
556 - // This means that the propgation chain can be stopped part of the the way
557 - // through processing event component instances.
514 + const visitedResponders = new Set();
515 let node = targetFiber;
516 while (node !== null) {
560 - const {dependencies, stateNode, tag} = node;
561 - if (tag === HostComponent) {
562 - responderEvent.currentTarget = stateNode;
563 - } else if (tag === EventComponent) {
564 - const eventComponentInstance = stateNode;
565 - // Switch to the current fiber tree
566 - node = eventComponentInstance.currentFiber;
567 - handleTargetEventResponderInstance(
568 - topLevelType,
569 - responderEvent,
570 - eventComponentInstance,
571 - hookComponentResponderValidation,
572 - propagatedEventResponders,
573 - );
574 - } else if (tag === FunctionComponent && dependencies !== null) {
575 - const events = dependencies.events;
576 - if (events !== null) {
577 - for (let i = 0; i < events.length; i++) {
578 - const eventComponentInstance = events[i];
579 - if (
580 - hookComponentResponderValidation.has(
581 - eventComponentInstance.responder,
582 - )
583 - ) {
584 - handleTargetEventResponderInstance(
585 - topLevelType,
586 - responderEvent,
587 - eventComponentInstance,
588 - null,
589 - null,
590 - );
517 + const {dependencies, tag} = node;
518 + if (tag === HostComponent && dependencies !== null) {
519 + const respondersMap = dependencies.responders;
520 + if (respondersMap !== null) {
521 + const responderInstances = Array.from(respondersMap.values());
522 + for (let i = 0, length = responderInstances.length; i < length; i++) {
523 + const responderInstance = responderInstances[i];
524 +
525 + if (validateOwnership(responderInstance)) {
526 + const {props, responder, state, target} = responderInstance;
527 + if (
528 + !visitedResponders.has(responder) &&
529 + validateResponderTargetEventTypes(eventType, responder)
530 + ) {
531 + const onEvent = responder.onEvent;
532 + visitedResponders.add(responder);
533 + if (onEvent !== null) {
534 + currentInstance = responderInstance;
535 + responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
536 + onEvent(responderEvent, eventResponderContext, props, state);
537 + }
538 + }
539 }
540 }
541 }
542 }
543 node = node.return;
544 }
597 - // Reset currentTarget to be null
598 - responderEvent.currentTarget = null;
545 // Root phase
600 - const rootEventInstances = rootEventTypesToEventComponentInstances.get(
601 - topLevelType,
546 + const rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
547 + eventType,
548 );
603 - if (rootEventInstances !== undefined) {
604 - const rootEventComponentInstances = Array.from(rootEventInstances);
549 + if (rootEventResponderInstances !== undefined) {
550 + const responderInstances = Array.from(rootEventResponderInstances);
551
606 - for (let i = 0; i < rootEventComponentInstances.length; i++) {
607 - const rootEventComponentInstance = rootEventComponentInstances[i];
608 - const {isHook, props, responder, state} = rootEventComponentInstance;
552 + for (let i = 0; i < responderInstances.length; i++) {
553 + const responderInstance = responderInstances[i];
554 + if (!validateOwnership(responderInstance)) {
555 + continue;
556 + }
557 + const {props, responder, state, target} = responderInstance;
558 const onRootEvent = responder.onRootEvent;
610 - if (onRootEvent !== undefined) {
611 - if (
612 - shouldSkipEventComponent(
613 - rootEventComponentInstance,
614 - responder,
615 - null,
616 - isHook,
617 - )
618 - ) {
619 - continue;
620 - }
621 - currentInstance = rootEventComponentInstance;
622 - currentlyInHook = isHook;
559 + if (onRootEvent !== null) {
560 + currentInstance = responderInstance;
561 + responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
562 onRootEvent(responderEvent, eventResponderContext, props, state);
563 }
564 }
@@ -637,9 +576,10 @@ export function dispatchEventForResponderEventSystem(
576 const previousInstance = currentInstance;
577 const previousTimers = currentTimers;
578 const previousTimeStamp = currentTimeStamp;
640 - const previouslyInHook = currentlyInHook;
579 + const previousEventQueuePriority = currentEventQueuePriority;
580 currentTimers = null;
642 - currentEventQueue = createEventQueue();
581 + currentEventQueue = [];
582 + currentEventQueuePriority = ContinuousEvent;
583 // We might want to control timeStamp another way here
584 currentTimeStamp = Date.now();
585 try {
@@ -654,25 +594,54 @@ export function dispatchEventForResponderEventSystem(
594 currentInstance = previousInstance;
595 currentEventQueue = previousEventQueue;
596 currentTimeStamp = previousTimeStamp;
657 - currentlyInHook = previouslyInHook;
597 + currentEventQueuePriority = previousEventQueuePriority;
598 + }
599 +}
600 +
601 +// TODO this function is almost an exact copy of the DOM version, we should
602 +// somehow share the logic
603 +function triggerOwnershipListeners(): void {
604 + const listeningInstances = Array.from(ownershipChangeListeners);
605 + const previousInstance = currentInstance;
606 + const previousEventQueuePriority = currentEventQueuePriority;
607 + const previousEventQueue = currentEventQueue;
608 + try {
609 + for (let i = 0; i < listeningInstances.length; i++) {
610 + const instance = listeningInstances[i];
611 + const {props, responder, state} = instance;
612 + currentInstance = instance;
613 + currentEventQueuePriority = ContinuousEvent;
614 + currentEventQueue = [];
615 + const onOwnershipChange = ((responder: any): ReactNativeEventResponder)
616 + .onOwnershipChange;
617 + if (onOwnershipChange !== null) {
618 + onOwnershipChange(eventResponderContext, props, state);
619 + }
620 + }
621 + processEventQueue();
622 + } finally {
623 + currentInstance = previousInstance;
624 + currentEventQueue = previousEventQueue;
625 + currentEventQueuePriority = previousEventQueuePriority;
626 }
627 }
628
629 // TODO this function is almost an exact copy of the DOM version, we should
630 // somehow share the logic
631 export function mountEventResponder(
664 - eventComponentInstance: ReactNativeEventComponentInstance,
632 + responder: ReactNativeEventResponder,
633 + responderInstance: ReactNativeEventResponderInstance,
634 + props: Object,
635 + state: Object,
636 ) {
666 - const responder = ((eventComponentInstance.responder: any): ReactNativeEventResponder);
667 - if (responder.onOwnershipChange !== undefined) {
668 - ownershipChangeListeners.add(eventComponentInstance);
637 + if (responder.onOwnershipChange !== null) {
638 + ownershipChangeListeners.add(responderInstance);
639 }
640 const onMount = responder.onMount;
671 - if (onMount !== undefined) {
672 - let {isHook, props, state} = eventComponentInstance;
673 - currentEventQueue = createEventQueue();
674 - currentInstance = eventComponentInstance;
675 - currentlyInHook = isHook;
641 + if (onMount !== null) {
642 + currentEventQueuePriority = ContinuousEvent;
643 + currentInstance = responderInstance;
644 + currentEventQueue = [];
645 try {
646 onMount(eventResponderContext, props, state);
647 processEventQueue();
@@ -687,15 +656,15 @@ export function mountEventResponder(
656 // TODO this function is almost an exact copy of the DOM version, we should
657 // somehow share the logic
658 export function unmountEventResponder(
690 - eventComponentInstance: ReactNativeEventComponentInstance,
659 + responderInstance: ReactNativeEventResponderInstance,
660 ): void {
692 - const responder = ((eventComponentInstance.responder: any): ReactNativeEventResponder);
661 + const responder = ((responderInstance.responder: any): ReactNativeEventResponder);
662 const onUnmount = responder.onUnmount;
694 - if (onUnmount !== undefined) {
695 - let {isHook, props, state} = eventComponentInstance;
696 - currentEventQueue = createEventQueue();
697 - currentInstance = eventComponentInstance;
698 - currentlyInHook = isHook;
663 + if (onUnmount !== null) {
664 + let {props, state} = responderInstance;
665 + currentEventQueue = [];
666 + currentEventQueuePriority = ContinuousEvent;
667 + currentInstance = responderInstance;
668 try {
669 onUnmount(eventResponderContext, props, state);
670 processEventQueue();
@@ -705,27 +674,21 @@ export function unmountEventResponder(
674 currentTimers = null;
675 }
676 }
708 - try {
709 - currentEventQueue = createEventQueue();
710 - releaseOwnershipForEventComponentInstance(eventComponentInstance);
711 - processEventQueue();
712 - } finally {
713 - currentEventQueue = null;
714 - }
715 - if (responder.onOwnershipChange !== undefined) {
716 - ownershipChangeListeners.delete(eventComponentInstance);
677 + releaseOwnershipForEventResponderInstance(responderInstance);
678 + if (responder.onOwnershipChange !== null) {
679 + ownershipChangeListeners.delete(responderInstance);
680 }
718 - const rootEventTypesSet = eventComponentInstance.rootEventTypes;
681 + const rootEventTypesSet = responderInstance.rootEventTypes;
682 if (rootEventTypesSet !== null) {
683 const rootEventTypes = Array.from(rootEventTypesSet);
684
685 for (let i = 0; i < rootEventTypes.length; i++) {
686 const topLevelEventType = rootEventTypes[i];
724 - let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
687 + let rootEventResponderInstances = rootEventTypesToEventResponderInstances.get(
688 topLevelEventType,
689 );
727 - if (rootEventComponentInstances !== undefined) {
728 - rootEventComponentInstances.delete(eventComponentInstance);
690 + if (rootEventResponderInstances !== undefined) {
691 + rootEventResponderInstances.delete(responderInstance);
692 }
693 }
694 }
@@ -733,14 +696,14 @@ export function unmountEventResponder(
696
697 function registerRootEventType(
698 rootEventType: string,
736 - eventComponentInstance: ReactNativeEventComponentInstance,
699 + eventComponentInstance: ReactNativeEventResponderInstance,
700 ) {
738 - let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
701 + let rootEventComponentInstances = rootEventTypesToEventResponderInstances.get(
702 rootEventType,
703 );
704 if (rootEventComponentInstances === undefined) {
705 rootEventComponentInstances = new Set();
743 - rootEventTypesToEventComponentInstances.set(
706 + rootEventTypesToEventResponderInstances.set(
707 rootEventType,
708 rootEventComponentInstances,
709 );
@@ -760,8 +723,8 @@ function registerRootEventType(
723 rootEventComponentInstances.add(eventComponentInstance);
724 }
725
763 -export function addRootEventTypesForComponentInstance(
764 - eventComponentInstance: ReactNativeEventComponentInstance,
726 +export function addRootEventTypesForResponderInstance(
727 + eventComponentInstance: ReactNativeEventResponderInstance,
728 rootEventTypes: Array<string>,
729 ): void {
730 for (let i = 0; i < rootEventTypes.length; i++) {
packages/react-native-renderer/src/ReactFabricHostConfig.js
+24 -28
@@ -16,7 +16,10 @@ import type {
16 ReactNativeResponderEvent,
17 ReactNativeResponderContext,
18 } from './ReactNativeTypes';
19 -import type {ReactEventComponentInstance} from 'shared/ReactTypes';
19 +import type {
20 + ReactEventResponder,
21 + ReactEventResponderInstance,
22 +} from 'shared/ReactTypes';
23
24 import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils';
25 import {create, diff} from './ReactNativeAttributePayload';
@@ -26,7 +29,7 @@ import warningWithoutStack from 'shared/warningWithoutStack';
29
30 import {dispatchEvent} from './ReactFabricEventEmitter';
31 import {
29 - addRootEventTypesForComponentInstance,
32 + addRootEventTypesForResponderInstance,
33 mountEventResponder,
34 unmountEventResponder,
35 } from './ReactFabricEventResponderSystem';
@@ -64,7 +67,12 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
67 // This means that they never overlap.
68 let nextReactTag = 2;
69
67 -type ReactNativeEventComponentInstance = ReactEventComponentInstance<
70 +type ReactNativeEventResponderInstance = ReactEventResponderInstance<
71 + ReactNativeResponderEvent,
72 + ReactNativeResponderContext,
73 +>;
74 +
75 +type ReactNativeEventResponder = ReactEventResponder<
76 ReactNativeResponderEvent,
77 ReactNativeResponderContext,
78 >;
@@ -296,13 +304,6 @@ export function getChildHostContext(
304 }
305 }
306
299 -export function getChildHostContextForEventComponent(
300 - parentHostContext: HostContext,
301 -) {
302 - // TODO: add getChildHostContextForEventComponent implementation
303 - return parentHostContext;
304 -}
305 -
307 export function getPublicInstance(instance: Instance): * {
308 return instance.canonical;
309 }
@@ -442,34 +443,29 @@ export function replaceContainerChildren(
443 newChildren: ChildSet,
444 ): void {}
445
445 -export function mountEventComponent(
446 - eventComponentInstance: ReactNativeEventComponentInstance,
446 +export function mountResponderInstance(
447 + responder: ReactNativeEventResponder,
448 + responderInstance: ReactNativeEventResponderInstance,
449 + props: Object,
450 + state: Object,
451 + instance: Instance,
452 + rootContainerInstance: Container,
453 ) {
454 if (enableFlareAPI) {
449 - const responder = eventComponentInstance.responder;
455 const {rootEventTypes} = responder;
451 - if (rootEventTypes !== undefined) {
452 - addRootEventTypesForComponentInstance(
453 - eventComponentInstance,
454 - rootEventTypes,
455 - );
456 + if (rootEventTypes !== null) {
457 + addRootEventTypesForResponderInstance(responderInstance, rootEventTypes);
458 }
457 - mountEventResponder(eventComponentInstance);
459 + mountEventResponder(responder, responderInstance, props, state);
460 }
461 }
462
461 -export function updateEventComponent(
462 - eventComponentInstance: ReactNativeEventComponentInstance,
463 -) {
464 - // NO-OP, why might use this in the future
465 -}
466 -
467 -export function unmountEventComponent(
468 - eventComponentInstance: ReactNativeEventComponentInstance,
463 +export function unmountResponderInstance(
464 + responderInstance: ReactNativeEventResponderInstance,
465 ): void {
466 if (enableFlareAPI) {
467 // TODO stop listening to targetEventTypes
472 - unmountEventResponder(eventComponentInstance);
468 + unmountEventResponder(responderInstance);
469 }
470 }
471
packages/react-native-renderer/src/ReactNativeHostConfig.js
+19 -19
@@ -12,7 +12,10 @@ import type {
12 ReactNativeResponderEvent,
13 ReactNativeResponderContext,
14 } from './ReactNativeTypes';
15 -import type {ReactEventComponentInstance} from 'shared/ReactTypes';
15 +import type {
16 + ReactEventResponder,
17 + ReactEventResponderInstance,
18 +} from 'shared/ReactTypes';
19
20 import invariant from 'shared/invariant';
21
@@ -33,7 +36,12 @@ import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
36
37 const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
38
36 -type ReactNativeEventComponentInstance = ReactEventComponentInstance<
39 +type ReactNativeEventResponderInstance = ReactEventResponderInstance<
40 + ReactNativeResponderEvent,
41 + ReactNativeResponderContext,
42 +>;
43 +
44 +type ReactNativeEventResponder = ReactEventResponder<
45 ReactNativeResponderEvent,
46 ReactNativeResponderContext,
47 >;
@@ -214,13 +222,6 @@ export function getChildHostContext(
222 }
223 }
224
217 -export function getChildHostContextForEventComponent(
218 - parentHostContext: HostContext,
219 -) {
220 - // TODO: add getChildHostContextForEventComponent implementation
221 - return parentHostContext;
222 -}
223 -
225 export function getPublicInstance(instance: Instance): * {
226 return instance;
227 }
@@ -494,20 +495,19 @@ export function unhideTextInstance(
495 throw new Error('Not yet implemented.');
496 }
497
497 -export function mountEventComponent(
498 - eventComponentInstance: ReactNativeEventComponentInstance,
499 -) {
500 - throw new Error('Not yet implemented.');
501 -}
502 -
503 -export function updateEventComponent(
504 - eventComponentInstance: ReactNativeEventComponentInstance,
498 +export function mountResponderInstance(
499 + responder: ReactNativeEventResponder,
500 + responderInstance: ReactNativeEventResponderInstance,
501 + props: Object,
502 + state: Object,
503 + instance: Instance,
504 + rootContainerInstance: Container,
505 ) {
506 throw new Error('Not yet implemented.');
507 }
508
509 -export function unmountEventComponent(
510 - eventComponentInstance: ReactNativeEventComponentInstance,
509 +export function unmountResponderInstance(
510 + responderInstance: ReactNativeEventResponderInstance,
511 ): void {
512 throw new Error('Not yet implemented.');
513 }
packages/react-native-renderer/src/ReactNativeTypes.js
+4 -4
@@ -190,16 +190,16 @@ export type ReactFaricEvent = {
190 };
191
192 export type ReactNativeResponderEvent = {
193 - currentTarget: null | ReactNativeEventTarget,
193 nativeEvent: ReactFaricEvent,
194 + responderTarget: null | ReactNativeEventTarget,
195 target: null | ReactNativeEventTarget,
196 type: string,
197 };
198
199 export type ReactNativeResponderContext = {
200 dispatchEvent: (
201 - eventObject: Object,
202 - listener: (Object) => void,
201 + eventProp: string,
202 + eventValue: any,
203 eventPriority: EventPriority,
204 ) => void,
205 isTargetWithinNode: (
@@ -230,7 +230,7 @@ export type PointerType =
230 | 'touch'
231 | 'trackpad';
232
233 -export opaque type EventPriority = 0 | 1 | 2;
233 +export type EventPriority = 0 | 1 | 2;
234
235 export const DiscreteEvent: EventPriority = 0;
236 export const UserBlockingEvent: EventPriority = 1;
packages/react-noop-renderer/src/createReactNoop.js
+3 -27
@@ -24,14 +24,10 @@ import * as Scheduler from 'scheduler/unstable_mock';
24 import {createPortal} from 'shared/ReactPortal';
25 import expect from 'expect';
26 import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
27 -import warning from 'shared/warning';
27 import enqueueTask from 'shared/enqueueTask';
28 import ReactSharedInternals from 'shared/ReactSharedInternals';
29 import warningWithoutStack from 'shared/warningWithoutStack';
31 -import {
32 - warnAboutMissingMockScheduler,
33 - enableFlareAPI,
34 -} from 'shared/ReactFeatureFlags';
30 +import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
31 import {ConcurrentRoot, BatchedRoot, LegacyRoot} from 'shared/ReactRootTags';
32
33 type Container = {
@@ -69,7 +65,6 @@ const {IsSomeRendererActing} = ReactSharedInternals;
65
66 const NO_CONTEXT = {};
67 const UPPERCASE_CONTEXT = {};
72 -const EVENT_COMPONENT_CONTEXT = {};
68 const UPDATE_SIGNAL = {};
69 if (__DEV__) {
70 Object.freeze(NO_CONTEXT);
@@ -266,13 +261,6 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
261 return NO_CONTEXT;
262 },
263
269 - getChildHostContextForEventComponent(parentHostContext: HostContext) {
270 - if (__DEV__ && enableFlareAPI) {
271 - return EVENT_COMPONENT_CONTEXT;
272 - }
273 - return parentHostContext;
274 - },
275 -
264 getPublicInstance(instance) {
265 return instance;
266 },
@@ -356,14 +344,6 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
344 hostContext: Object,
345 internalInstanceHandle: Object,
346 ): TextInstance {
359 - if (__DEV__ && enableFlareAPI) {
360 - warning(
361 - hostContext !== EVENT_COMPONENT_CONTEXT,
362 - 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
363 - 'Wrap the child text "%s" in an element.',
364 - text,
365 - );
366 - }
347 if (hostContext === UPPERCASE_CONTEXT) {
348 text = text.toUpperCase();
349 }
@@ -396,15 +376,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
376 warnsIfNotActing: true,
377 supportsHydration: false,
378
399 - mountEventComponent(): void {
400 - // NO-OP
401 - },
402 -
403 - updateEventComponent(): void {
379 + mountResponderInstance(): void {
380 // NO-OP
381 },
382
407 - unmountEventComponent(): void {
383 + unmountResponderInstance(): void {
384 // NO-OP
385 },
386
packages/react-reconciler/src/ReactFiber.js
+14 -33
@@ -12,7 +12,8 @@ import type {
12 ReactFragment,
13 ReactPortal,
14 RefObject,
15 - ReactEventComponent,
15 + ReactEventResponder,
16 + ReactEventResponderInstance,
17 ReactFundamentalComponent,
18 } from 'shared/ReactTypes';
19 import type {RootTag} from 'shared/ReactRootTags';
@@ -23,13 +24,11 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
24 import type {UpdateQueue} from './ReactUpdateQueue';
25 import type {ContextDependency} from './ReactFiberNewContext';
26 import type {HookType} from './ReactFiberHooks';
26 -import type {ReactEventComponentInstance} from 'shared/ReactTypes';
27
28 import invariant from 'shared/invariant';
29 import warningWithoutStack from 'shared/warningWithoutStack';
30 import {
31 enableProfilerTimer,
32 - enableFlareAPI,
32 enableFundamentalAPI,
33 } from 'shared/ReactFeatureFlags';
34 import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
@@ -53,7 +52,6 @@ import {
52 MemoComponent,
53 SimpleMemoComponent,
54 LazyComponent,
56 - EventComponent,
55 FundamentalComponent,
56 } from 'shared/ReactWorkTags';
57 import getComponentName from 'shared/getComponentName';
@@ -84,7 +82,6 @@ import {
82 REACT_SUSPENSE_LIST_TYPE,
83 REACT_MEMO_TYPE,
84 REACT_LAZY_TYPE,
87 - REACT_EVENT_COMPONENT_TYPE,
85 REACT_FUNDAMENTAL_TYPE,
86 } from 'shared/ReactSymbols';
87
@@ -110,7 +107,14 @@ if (__DEV__) {
107 export type Dependencies = {
108 expirationTime: ExpirationTime,
109 firstContext: ContextDependency<mixed> | null,
113 - events: Array<ReactEventComponentInstance<any, any>> | null,
110 + listeners: Array<{
111 + responder: ReactEventResponder<any, any>,
112 + props: Object,
113 + }> | null,
114 + responders: Map<
115 + ReactEventResponder<any, any>,
116 + ReactEventResponderInstance<any, any>,
117 + > | null,
118 };
119
120 // A Fiber is work on a Component that needs to be done or was done. There can
@@ -453,7 +457,8 @@ export function createWorkInProgress(
457 : {
458 expirationTime: currentDependencies.expirationTime,
459 firstContext: currentDependencies.firstContext,
456 - events: currentDependencies.events,
460 + listeners: currentDependencies.listeners,
461 + responders: currentDependencies.responders,
462 };
463
464 // These will be overridden during the parent's reconciliation
@@ -548,7 +553,8 @@ export function resetWorkInProgress(
553 : {
554 expirationTime: currentDependencies.expirationTime,
555 firstContext: currentDependencies.firstContext,
551 - events: currentDependencies.events,
556 + listeners: currentDependencies.listeners,
557 + responders: currentDependencies.responders,
558 };
559
560 if (enableProfilerTimer) {
@@ -659,17 +665,6 @@ export function createFiberFromTypeAndProps(
665 fiberTag = LazyComponent;
666 resolvedType = null;
667 break getTag;
662 - case REACT_EVENT_COMPONENT_TYPE:
663 - if (enableFlareAPI) {
664 - return createFiberFromEventComponent(
665 - type,
666 - pendingProps,
667 - mode,
668 - expirationTime,
669 - key,
670 - );
671 - }
672 - break;
668 case REACT_FUNDAMENTAL_TYPE:
669 if (enableFundamentalAPI) {
670 return createFiberFromFundamental(
@@ -759,20 +754,6 @@ export function createFiberFromFragment(
754 return fiber;
755 }
756
762 -export function createFiberFromEventComponent(
763 - eventComponent: ReactEventComponent<any, any, any>,
764 - pendingProps: any,
765 - mode: TypeOfMode,
766 - expirationTime: ExpirationTime,
767 - key: null | string,
768 -): Fiber {
769 - const fiber = createFiber(EventComponent, pendingProps, key, mode);
770 - fiber.elementType = eventComponent;
771 - fiber.type = eventComponent;
772 - fiber.expirationTime = expirationTime;
773 - return fiber;
774 -}
775 -
757 export function createFiberFromFundamental(
758 fundamentalComponent: ReactFundamentalComponent<any, any>,
759 pendingProps: any,
packages/react-reconciler/src/ReactFiberBeginWork.js
+20 -39
@@ -41,7 +41,6 @@ import {
41 SimpleMemoComponent,
42 LazyComponent,
43 IncompleteClassComponent,
44 - EventComponent,
44 FundamentalComponent,
45 } from 'shared/ReactWorkTags';
46 import {
@@ -111,11 +110,7 @@ import {
110 } from './ReactFiberHostConfig';
111 import type {SuspenseInstance} from './ReactFiberHostConfig';
112 import {shouldSuspend} from './ReactFiberReconciler';
114 -import {
115 - pushHostContext,
116 - pushHostContainer,
117 - pushHostContextForEventComponent,
118 -} from './ReactFiberHostContext';
113 +import {pushHostContext, pushHostContainer} from './ReactFiberHostContext';
114 import {
115 suspenseStackCursor,
116 pushSuspenseContext,
@@ -177,7 +172,7 @@ import {
172 requestCurrentTime,
173 retryTimedOutBoundary,
174 } from './ReactFiberWorkLoop';
180 -import {prepareToReadEventComponents} from './ReactFiberEvents';
175 +import {prepareToReadListenerHooks} from './ReactFiberEvents';
176
177 const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
178
@@ -304,7 +299,9 @@ function updateForwardRef(
299 // The rest is a fork of updateFunctionComponent
300 let nextChildren;
301 prepareToReadContext(workInProgress, renderExpirationTime);
307 - prepareToReadEventComponents(workInProgress);
302 + if (enableFlareAPI) {
303 + prepareToReadListenerHooks(workInProgress);
304 + }
305 if (__DEV__) {
306 ReactCurrentOwner.current = workInProgress;
307 setCurrentPhase('render');
@@ -323,6 +320,9 @@ function updateForwardRef(
320 ) {
321 // Only double-render components with Hooks
322 if (workInProgress.memoizedState !== null) {
323 + if (enableFlareAPI) {
324 + prepareToReadListenerHooks(workInProgress);
325 + }
326 nextChildren = renderWithHooks(
327 current,
328 workInProgress,
@@ -625,7 +625,9 @@ function updateFunctionComponent(
625
626 let nextChildren;
627 prepareToReadContext(workInProgress, renderExpirationTime);
628 - prepareToReadEventComponents(workInProgress);
628 + if (enableFlareAPI) {
629 + prepareToReadListenerHooks(workInProgress);
630 + }
631 if (__DEV__) {
632 ReactCurrentOwner.current = workInProgress;
633 setCurrentPhase('render');
@@ -644,6 +646,9 @@ function updateFunctionComponent(
646 ) {
647 // Only double-render components with Hooks
648 if (workInProgress.memoizedState !== null) {
649 + if (enableFlareAPI) {
650 + prepareToReadListenerHooks(workInProgress);
651 + }
652 nextChildren = renderWithHooks(
653 current,
654 workInProgress,
@@ -1240,7 +1245,9 @@ function mountIndeterminateComponent(
1245 const context = getMaskedContext(workInProgress, unmaskedContext);
1246
1247 prepareToReadContext(workInProgress, renderExpirationTime);
1243 - prepareToReadEventComponents(workInProgress);
1248 + if (enableFlareAPI) {
1249 + prepareToReadListenerHooks(workInProgress);
1250 + }
1251 let value;
1252
1253 if (__DEV__) {
@@ -1363,6 +1370,9 @@ function mountIndeterminateComponent(
1370 ) {
1371 // Only double-render components with Hooks
1372 if (workInProgress.memoizedState !== null) {
1373 + if (enableFlareAPI) {
1374 + prepareToReadListenerHooks(workInProgress);
1375 + }
1376 value = renderWithHooks(
1377 null,
1378 workInProgress,
@@ -2479,20 +2489,6 @@ function updateContextConsumer(
2489 return workInProgress.child;
2490 }
2491
2482 -function updateEventComponent(current, workInProgress, renderExpirationTime) {
2483 - const nextProps = workInProgress.pendingProps;
2484 - const nextChildren = nextProps.children;
2485 -
2486 - reconcileChildren(
2487 - current,
2488 - workInProgress,
2489 - nextChildren,
2490 - renderExpirationTime,
2491 - );
2492 - pushHostContextForEventComponent(workInProgress);
2493 - return workInProgress.child;
2494 -}
2495 -
2492 function updateFundamentalComponent(
2493 current,
2494 workInProgress,
@@ -2803,11 +2799,6 @@ function beginWork(
2799 pushSuspenseContext(workInProgress, suspenseStackCursor.current);
2800 break;
2801 }
2806 - case EventComponent:
2807 - if (enableFlareAPI) {
2808 - pushHostContextForEventComponent(workInProgress);
2809 - }
2810 - break;
2802 }
2803 return bailoutOnAlreadyFinishedWork(
2804 current,
@@ -2993,16 +2984,6 @@ function beginWork(
2984 renderExpirationTime,
2985 );
2986 }
2996 - case EventComponent: {
2997 - if (enableFlareAPI) {
2998 - return updateEventComponent(
2999 - current,
3000 - workInProgress,
3001 - renderExpirationTime,
3002 - );
3003 - }
3004 - break;
3005 - }
2987 case FundamentalComponent: {
2988 if (enableFundamentalAPI) {
2989 return updateFundamentalComponent(
packages/react-reconciler/src/ReactFiberCommitWork.js
+21 -22
@@ -45,7 +45,6 @@ import {
45 IncompleteClassComponent,
46 MemoComponent,
47 SimpleMemoComponent,
48 - EventComponent,
48 SuspenseListComponent,
49 FundamentalComponent,
50 } from 'shared/ReactWorkTags';
@@ -94,8 +93,7 @@ import {
93 hideTextInstance,
94 unhideInstance,
95 unhideTextInstance,
97 - unmountEventComponent,
98 - mountEventComponent,
96 + unmountResponderInstance,
97 unmountFundamentalComponent,
98 updateFundamentalComponent,
99 } from './ReactFiberHostConfig';
@@ -596,12 +594,6 @@ function commitLifeCycles(
594 case IncompleteClassComponent:
595 case FundamentalComponent:
596 return;
599 - case EventComponent: {
600 - if (enableFlareAPI) {
601 - mountEventComponent(finishedWork.stateNode);
602 - }
603 - return;
604 - }
597 default: {
598 invariant(
599 false,
@@ -742,6 +734,25 @@ function commitUnmount(current: Fiber): void {
734 return;
735 }
736 case HostComponent: {
737 + if (enableFlareAPI) {
738 + const dependencies = current.dependencies;
739 +
740 + if (dependencies !== null) {
741 + const respondersMap = dependencies.responders;
742 + if (respondersMap !== null) {
743 + const responderInstances = Array.from(respondersMap.values());
744 + for (
745 + let i = 0, length = responderInstances.length;
746 + i < length;
747 + i++
748 + ) {
749 + const responderInstance = responderInstances[i];
750 + unmountResponderInstance(responderInstance);
751 + }
752 + dependencies.responders = null;
753 + }
754 + }
755 + }
756 safelyDetachRef(current);
757 return;
758 }
@@ -756,14 +767,6 @@ function commitUnmount(current: Fiber): void {
767 }
768 return;
769 }
759 - case EventComponent: {
760 - if (enableFlareAPI) {
761 - const eventComponentInstance = current.stateNode;
762 - unmountEventComponent(eventComponentInstance);
763 - current.stateNode = null;
764 - }
765 - break;
766 - }
770 case FundamentalComponent: {
771 if (enableFundamentalAPI) {
772 const fundamentalInstance = current.stateNode;
@@ -853,8 +856,7 @@ function commitContainer(finishedWork: Fiber) {
856 case ClassComponent:
857 case HostComponent:
858 case HostText:
856 - case FundamentalComponent:
857 - case EventComponent: {
859 + case FundamentalComponent: {
860 return;
861 }
862 case HostRoot:
@@ -1285,9 +1287,6 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1287 case IncompleteClassComponent: {
1288 return;
1289 }
1288 - case EventComponent: {
1289 - return;
1290 - }
1290 case FundamentalComponent: {
1291 if (enableFundamentalAPI) {
1292 const fundamentalInstance = finishedWork.stateNode;
packages/react-reconciler/src/ReactFiberCompleteWork.js
+180 -43
@@ -9,6 +9,11 @@
9
10 import type {Fiber} from './ReactFiber';
11 import type {ExpirationTime} from './ReactFiberExpirationTime';
12 +import type {
13 + ReactEventResponder,
14 + ReactEventResponderInstance,
15 + ReactFundamentalComponentInstance,
16 +} from 'shared/ReactTypes';
17 import type {FiberRoot} from './ReactFiberRoot';
18 import type {
19 Instance,
@@ -17,10 +22,6 @@ import type {
22 Container,
23 ChildSet,
24 } from './ReactFiberHostConfig';
20 -import type {
21 - ReactEventComponentInstance,
22 - ReactFundamentalComponentInstance,
23 -} from 'shared/ReactTypes';
25 import type {
26 SuspenseState,
27 SuspenseListRenderState,
@@ -29,6 +30,7 @@ import type {SuspenseContext} from './ReactFiberSuspenseContext';
30
31 import {now} from './SchedulerWithReactIntegration';
32
33 +import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
34 import {
35 IndeterminateComponent,
36 FunctionComponent,
@@ -50,7 +52,6 @@ import {
52 SimpleMemoComponent,
53 LazyComponent,
54 IncompleteClassComponent,
53 - EventComponent,
55 FundamentalComponent,
56 } from 'shared/ReactWorkTags';
57 import {NoMode, BatchedMode} from './ReactTypeOfMode';
@@ -78,7 +79,8 @@ import {
79 createContainerChildSet,
80 appendChildToContainerChildSet,
81 finalizeContainerChildren,
81 - updateEventComponent,
82 + mountResponderInstance,
83 + unmountResponderInstance,
84 getFundamentalComponentInstance,
85 mountFundamentalComponent,
86 cloneFundamentalInstance,
@@ -90,6 +92,8 @@ import {
92 getHostContext,
93 popHostContainer,
94 } from './ReactFiberHostContext';
95 +import {NoWork} from './ReactFiberExpirationTime';
96 +import {createResponderInstance} from './ReactFiberEvents';
97 import {
98 suspenseStackCursor,
99 InvisibleParentSuspenseContext,
@@ -125,11 +129,13 @@ import {
129 renderDidSuspendDelayIfPossible,
130 renderHasNotSuspendedYet,
131 } from './ReactFiberWorkLoop';
128 -import {createEventComponentInstance} from './ReactFiberEvents';
132 import {createFundamentalStateInstance} from './ReactFiberFundamental';
133 import {Never} from './ReactFiberExpirationTime';
134 import {resetChildFibers} from './ReactChildFiber';
135
136 +const emptyObject = {};
137 +const isArray = Array.isArray;
138 +
139 function markUpdate(workInProgress: Fiber) {
140 // Tag the fiber with an update effect. This turns a Placement into
141 // a PlacementAndUpdate.
@@ -682,6 +688,20 @@ function completeWork(
688 rootContainerInstance,
689 );
690
691 + if (enableFlareAPI) {
692 + const prevResponders = current.memoizedProps.responders;
693 + const nextResponders = newProps.responders;
694 + const instance = workInProgress.stateNode;
695 + if (prevResponders !== nextResponders) {
696 + updateEventResponders(
697 + nextResponders,
698 + instance,
699 + rootContainerInstance,
700 + workInProgress,
701 + );
702 + }
703 + }
704 +
705 if (current.ref !== workInProgress.ref) {
706 markRef(workInProgress);
707 }
@@ -727,6 +747,18 @@ function completeWork(
747
748 appendAllChildren(instance, workInProgress, false, false);
749
750 + if (enableFlareAPI) {
751 + const responders = newProps.responders;
752 + if (responders != null) {
753 + updateEventResponders(
754 + responders,
755 + instance,
756 + rootContainerInstance,
757 + workInProgress,
758 + );
759 + }
760 + }
761 +
762 // Certain renderers require commit-time effects for initial mount.
763 // (eg DOM renderer supports auto-focus for certain elements).
764 // Make sure such renderers get scheduled for later work.
@@ -1133,42 +1165,6 @@ function completeWork(
1165 }
1166 break;
1167 }
1136 - case EventComponent: {
1137 - if (enableFlareAPI) {
1138 - popHostContext(workInProgress);
1139 - const rootContainerInstance = getRootHostContainer();
1140 - const responder = workInProgress.type.responder;
1141 - let eventComponentInstance: ReactEventComponentInstance<
1142 - any,
1143 - any,
1144 - > | null =
1145 - workInProgress.stateNode;
1146 -
1147 - if (eventComponentInstance === null) {
1148 - let responderState = null;
1149 - const getInitialState = responder.getInitialState;
1150 - if (getInitialState !== undefined) {
1151 - responderState = getInitialState(newProps);
1152 - }
1153 - eventComponentInstance = workInProgress.stateNode = createEventComponentInstance(
1154 - workInProgress,
1155 - newProps,
1156 - responder,
1157 - rootContainerInstance,
1158 - responderState || {},
1159 - false,
1160 - );
1161 - markUpdate(workInProgress);
1162 - } else {
1163 - // Update the props on the event component state node
1164 - eventComponentInstance.props = newProps;
1165 - // Update the current fiber
1166 - eventComponentInstance.currentFiber = workInProgress;
1167 - updateEventComponent(eventComponentInstance);
1168 - }
1169 - }
1170 - break;
1171 - }
1168 case FundamentalComponent: {
1169 if (enableFundamentalAPI) {
1170 const fundamentalImpl = workInProgress.type.impl;
@@ -1231,4 +1227,145 @@ function completeWork(
1227 return null;
1228 }
1229
1230 +function mountEventResponder(
1231 + responder: ReactEventResponder<any, any>,
1232 + responderProps: Object,
1233 + instance: Instance,
1234 + rootContainerInstance: Container,
1235 + fiber: Fiber,
1236 + respondersMap: Map<
1237 + ReactEventResponder<any, any>,
1238 + ReactEventResponderInstance<any, any>,
1239 + >,
1240 +) {
1241 + let responderState = emptyObject;
1242 + const getInitialState = responder.getInitialState;
1243 + if (getInitialState !== null) {
1244 + responderState = getInitialState(responderProps);
1245 + }
1246 + const responderInstance = createResponderInstance(
1247 + responder,
1248 + responderProps,
1249 + responderState,
1250 + instance,
1251 + fiber,
1252 + );
1253 + mountResponderInstance(
1254 + responder,
1255 + responderInstance,
1256 + responderProps,
1257 + responderState,
1258 + instance,
1259 + rootContainerInstance,
1260 + );
1261 + respondersMap.set(responder, responderInstance);
1262 +}
1263 +
1264 +function updateEventResponder(
1265 + responder: ReactEventResponder<any, any>,
1266 + props: Object,
1267 + fiber: Fiber,
1268 + visistedResponders: Set<ReactEventResponder<any, any>>,
1269 + respondersMap: Map<
1270 + ReactEventResponder<any, any>,
1271 + ReactEventResponderInstance<any, any>,
1272 + >,
1273 + instance: Instance,
1274 + rootContainerInstance: Container,
1275 +): void {
1276 + invariant(
1277 + responder && responder.$$typeof === REACT_RESPONDER_TYPE,
1278 + 'An invalid value was used as an event responder. Expect one or many event ' +
1279 + 'responders created via React.unstable_createResponer().',
1280 + );
1281 + if (visistedResponders.has(responder)) {
1282 + // show warning
1283 + return;
1284 + }
1285 + visistedResponders.add(responder);
1286 + const responderInstance = respondersMap.get(responder);
1287 +
1288 + if (responderInstance === undefined) {
1289 + // Mount
1290 + mountEventResponder(
1291 + responder,
1292 + props,
1293 + instance,
1294 + rootContainerInstance,
1295 + fiber,
1296 + respondersMap,
1297 + );
1298 + } else {
1299 + // Update
1300 + responderInstance.props = props;
1301 + responderInstance.fiber = fiber;
1302 + }
1303 +}
1304 +
1305 +function updateEventResponders(
1306 + responders: any,
1307 + instance: Instance,
1308 + rootContainerInstance: Container,
1309 + fiber: Fiber,
1310 +): void {
1311 + const visistedResponders = new Set();
1312 + let dependencies = fiber.dependencies;
1313 + if (responders != null) {
1314 + if (dependencies === null) {
1315 + dependencies = fiber.dependencies = {
1316 + expirationTime: NoWork,
1317 + firstContext: null,
1318 + listeners: null,
1319 + responders: new Map(),
1320 + };
1321 + }
1322 + let respondersMap = dependencies.responders;
1323 + if (respondersMap === null) {
1324 + respondersMap = new Map();
1325 + }
1326 + if (isArray(responders)) {
1327 + for (let i = 0, length = responders.length; i < length; i++) {
1328 + const {type, props} = responders[i];
1329 + updateEventResponder(
1330 + type,
1331 + props,
1332 + fiber,
1333 + visistedResponders,
1334 + respondersMap,
1335 + instance,
1336 + rootContainerInstance,
1337 + );
1338 + }
1339 + } else {
1340 + const {type, props} = responders;
1341 + updateEventResponder(
1342 + type,
1343 + props,
1344 + fiber,
1345 + visistedResponders,
1346 + respondersMap,
1347 + instance,
1348 + rootContainerInstance,
1349 + );
1350 + }
1351 + }
1352 + if (dependencies !== null) {
1353 + const respondersMap = dependencies.responders;
1354 + if (respondersMap !== null) {
1355 + // Unmount
1356 + const mountedResponders = Array.from(respondersMap.keys());
1357 + for (let i = 0, length = mountedResponders.length; i < length; i++) {
1358 + const mountedResponder = mountedResponders[i];
1359 + if (!visistedResponders.has(mountedResponder)) {
1360 + const responderInstance = ((respondersMap.get(
1361 + mountedResponder,
1362 + ): any): ReactEventResponderInstance<any, any>);
1363 + unmountResponderInstance(responderInstance);
1364 + respondersMap.delete(mountedResponder);
1365 + }
1366 + }
1367 + }
1368 + }
1369 +}
1370 +
1371 export {completeWork};
packages/react-reconciler/src/ReactFiberEvents.js
+47 -96
@@ -9,90 +9,61 @@
9
10 import type {Fiber, Dependencies} from './ReactFiber';
11 import type {
12 - ReactEventComponent,
12 ReactEventResponder,
14 - ReactEventComponentInstance,
13 + ReactEventResponderInstance,
14 } from 'shared/ReactTypes';
15 +import type {Instance} from './ReactFiberHostConfig';
16
17 -import {
18 - HostComponent,
19 - HostText,
20 - HostPortal,
21 - SuspenseComponent,
22 - Fragment,
23 -} from 'shared/ReactWorkTags';
17 import {NoWork} from './ReactFiberExpirationTime';
18
19 +import {SuspenseComponent, Fragment} from 'shared/ReactWorkTags';
20 +
21 let currentlyRenderingFiber: null | Fiber = null;
27 -let currentEventComponentInstanceIndex: number = 0;
22 +let currentListenerHookIndex: number = 0;
23
29 -export function prepareToReadEventComponents(workInProgress: Fiber): void {
24 +export function prepareToReadListenerHooks(workInProgress: Fiber): void {
25 currentlyRenderingFiber = workInProgress;
31 - currentEventComponentInstanceIndex = 0;
26 + currentListenerHookIndex = 0;
27 }
28
34 -export function updateEventComponentInstance<E, C>(
35 - eventComponent: ReactEventComponent<E, C>,
29 +function getListenerHooks(): Array<{
30 + responder: ReactEventResponder<any, any>,
31 props: Object,
37 -): void {
38 - const responder = eventComponent.responder;
39 - let events;
32 +}> {
33 + let listeners;
34 let dependencies: Dependencies | null = ((currentlyRenderingFiber: any): Fiber)
35 .dependencies;
36 if (dependencies === null) {
43 - events = [];
37 dependencies = ((currentlyRenderingFiber: any): Fiber).dependencies = {
38 expirationTime: NoWork,
39 firstContext: null,
47 - events,
40 + listeners: [],
41 + responders: null,
42 };
49 - } else {
50 - events = dependencies.events;
51 - if (events === null) {
52 - dependencies.events = events = [];
53 - }
43 }
55 - if (currentEventComponentInstanceIndex === events.length) {
56 - let responderState = null;
57 - const getInitialState = responder.getInitialState;
58 - if (getInitialState !== undefined) {
59 - responderState = getInitialState(props);
60 - }
61 - const eventComponentInstance = createEventComponentInstance(
62 - ((currentlyRenderingFiber: any): Fiber),
63 - props,
64 - responder,
65 - null,
66 - responderState || {},
67 - true,
68 - );
69 - events.push(eventComponentInstance);
70 - currentEventComponentInstanceIndex++;
71 - } else {
72 - const eventComponentInstance = events[currentEventComponentInstanceIndex++];
73 - eventComponentInstance.responder = responder;
74 - eventComponentInstance.props = props;
75 - eventComponentInstance.currentFiber = ((currentlyRenderingFiber: any): Fiber);
44 + listeners = dependencies.listeners;
45 + if (listeners === null) {
46 + dependencies.listeners = listeners = [];
47 }
48 + return listeners;
49 }
50
79 -export function createEventComponentInstance<E, C>(
80 - currentFiber: Fiber,
51 +export function updateListenerHook(
52 + responder: ReactEventResponder<any, any>,
53 props: Object,
82 - responder: ReactEventResponder<E, C>,
83 - rootInstance: mixed,
84 - state: Object,
85 - isHook: boolean,
86 -): ReactEventComponentInstance<E, C> {
87 - return {
88 - currentFiber,
89 - isHook,
90 - props,
91 - responder,
92 - rootEventTypes: null,
93 - rootInstance,
94 - state,
95 - };
54 +) {
55 + const listeners = getListenerHooks();
56 + if (listeners.length === currentListenerHookIndex) {
57 + listeners.push({
58 + responder,
59 + props,
60 + });
61 + currentListenerHookIndex++;
62 + } else {
63 + const currentListenerHook = listeners[currentListenerHookIndex++];
64 + currentListenerHook.responder = responder;
65 + currentListenerHook.props = props;
66 + }
67 }
68
69 export function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
@@ -126,39 +97,19 @@ export function getSuspenseFiberFromTimedOutChild(fiber: Fiber): Fiber {
97 return ((((fiber.return: any): Fiber).return: any): Fiber);
98 }
99
129 -export function getEventComponentHostChildrenCount(
130 - eventComponentFiber: Fiber,
131 -): ?number {
132 - if (__DEV__) {
133 - let hostChildrenCount = 0;
134 - const getHostChildrenCount = node => {
135 - if (isFiberSuspenseAndTimedOut(node)) {
136 - const fallbackChild = getSuspenseFallbackChild(node);
137 - if (fallbackChild !== null) {
138 - getHostChildrenCount(fallbackChild);
139 - }
140 - } else if (
141 - node.tag === HostComponent ||
142 - node.tag === HostText ||
143 - node.tag === HostPortal
144 - ) {
145 - hostChildrenCount++;
146 - } else {
147 - const child = node.child;
148 - if (child !== null) {
149 - getHostChildrenCount(child);
150 - }
151 - }
152 - const sibling = node.sibling;
153 - if (sibling !== null) {
154 - getHostChildrenCount(sibling);
155 - }
156 - };
157 -
158 - if (eventComponentFiber.child !== null) {
159 - getHostChildrenCount(eventComponentFiber.child);
160 - }
161 -
162 - return hostChildrenCount;
163 - }
100 +export function createResponderInstance(
101 + responder: ReactEventResponder<any, any>,
102 + responderProps: Object,
103 + responderState: Object,
104 + target: Instance,
105 + fiber: Fiber,
106 +): ReactEventResponderInstance<any, any> {
107 + return {
108 + fiber,
109 + props: responderProps,
110 + responder,
111 + rootEventTypes: null,
112 + state: responderState,
113 + target,
114 + };
115 }
packages/react-reconciler/src/ReactFiberHooks.js
+22 -25
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {ReactEventComponent, ReactContext} from 'shared/ReactTypes';
10 +import type {ReactEventResponder, ReactContext} from 'shared/ReactTypes';
11 import type {SideEffectTag} from 'shared/ReactSideEffectTags';
12 import type {Fiber} from './ReactFiber';
13 import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -19,7 +19,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
19
20 import {NoWork} from './ReactFiberExpirationTime';
21 import {readContext} from './ReactFiberNewContext';
22 -import {updateEventComponentInstance} from './ReactFiberEvents';
22 +import {updateListenerHook} from './ReactFiberEvents';
23 import {
24 Update as UpdateEffect,
25 Passive as PassiveEffect,
@@ -85,10 +85,7 @@ export type Dispatcher = {
85 deps: Array<mixed> | void | null,
86 ): void,
87 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
88 - useEvent<E, C>(
89 - eventComponent: ReactEventComponent<E, C>,
90 - props: Object,
91 - ): void,
88 + useListener<E, C>(responder: ReactEventResponder<E, C>, props: Object): void,
89 };
90
91 type Update<S, A> = {
@@ -120,7 +117,7 @@ export type HookType =
117 | 'useMemo'
118 | 'useImperativeHandle'
119 | 'useDebugValue'
123 - | 'useEvent';
120 + | 'useListener';
121
122 let didWarnAboutMismatchedHooksForComponent;
123 if (__DEV__) {
@@ -1265,7 +1262,7 @@ export const ContextOnlyDispatcher: Dispatcher = {
1262 useRef: throwInvalidHookError,
1263 useState: throwInvalidHookError,
1264 useDebugValue: throwInvalidHookError,
1268 - useEvent: updateEventComponentInstance,
1265 + useListener: throwInvalidHookError,
1266 };
1267
1268 const HooksDispatcherOnMount: Dispatcher = {
@@ -1281,7 +1278,7 @@ const HooksDispatcherOnMount: Dispatcher = {
1278 useRef: mountRef,
1279 useState: mountState,
1280 useDebugValue: mountDebugValue,
1284 - useEvent: updateEventComponentInstance,
1281 + useListener: updateListenerHook,
1282 };
1283
1284 const HooksDispatcherOnUpdate: Dispatcher = {
@@ -1297,7 +1294,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
1294 useRef: updateRef,
1295 useState: updateState,
1296 useDebugValue: updateDebugValue,
1300 - useEvent: updateEventComponentInstance,
1297 + useListener: updateListenerHook,
1298 };
1299
1300 let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
@@ -1427,10 +1424,10 @@ if (__DEV__) {
1424 mountHookTypesDev();
1425 return mountDebugValue(value, formatterFn);
1426 },
1430 - useEvent<E, C>(eventComponent: ReactEventComponent<E, C>, props) {
1431 - currentHookNameInDev = 'useEvent';
1427 + useListener<E, C>(responder: ReactEventResponder<E, C>, props) {
1428 + currentHookNameInDev = 'useListener';
1429 mountHookTypesDev();
1433 - updateEventComponentInstance(eventComponent, props);
1430 + updateListenerHook(responder, props);
1431 },
1432 };
1433
@@ -1529,10 +1526,10 @@ if (__DEV__) {
1526 updateHookTypesDev();
1527 return mountDebugValue(value, formatterFn);
1528 },
1532 - useEvent<E, C>(eventComponent: ReactEventComponent<E, C>, props) {
1533 - currentHookNameInDev = 'useEvent';
1529 + useListener<E, C>(responder: ReactEventResponder<E, C>, props) {
1530 + currentHookNameInDev = 'useListener';
1531 updateHookTypesDev();
1535 - updateEventComponentInstance(eventComponent, props);
1532 + updateListenerHook(responder, props);
1533 },
1534 };
1535
@@ -1631,10 +1628,10 @@ if (__DEV__) {
1628 updateHookTypesDev();
1629 return updateDebugValue(value, formatterFn);
1630 },
1634 - useEvent<E, C>(eventComponent: ReactEventComponent<E, C>, props) {
1635 - currentHookNameInDev = 'useEvent';
1631 + useListener<E, C>(responder: ReactEventResponder<E, C>, props) {
1632 + currentHookNameInDev = 'useListener';
1633 updateHookTypesDev();
1637 - updateEventComponentInstance(eventComponent, props);
1634 + updateListenerHook(responder, props);
1635 },
1636 };
1637
@@ -1744,11 +1741,11 @@ if (__DEV__) {
1741 mountHookTypesDev();
1742 return mountDebugValue(value, formatterFn);
1743 },
1747 - useEvent<E, C>(eventComponent: ReactEventComponent<E, C>, props) {
1748 - currentHookNameInDev = 'useEvent';
1744 + useListener<E, C>(responder: ReactEventResponder<E, C>, props) {
1745 + currentHookNameInDev = 'useListener';
1746 warnInvalidHookAccess();
1747 mountHookTypesDev();
1751 - updateEventComponentInstance(eventComponent, props);
1748 + updateListenerHook(responder, props);
1749 },
1750 };
1751
@@ -1858,11 +1855,11 @@ if (__DEV__) {
1855 updateHookTypesDev();
1856 return updateDebugValue(value, formatterFn);
1857 },
1861 - useEvent<E, C>(eventComponent: ReactEventComponent<E, C>, props) {
1862 - currentHookNameInDev = 'useEvent';
1858 + useListener<E, C>(responder: ReactEventResponder<E, C>, props) {
1859 + currentHookNameInDev = 'useListener';
1860 warnInvalidHookAccess();
1861 updateHookTypesDev();
1865 - updateEventComponentInstance(eventComponent, props);
1862 + updateListenerHook(responder, props);
1863 },
1864 };
1865 }
packages/react-reconciler/src/ReactFiberHostContext.js
+1 -21
@@ -13,11 +13,7 @@ import type {Container, HostContext} from './ReactFiberHostConfig';
13
14 import invariant from 'shared/invariant';
15
16 -import {
17 - getChildHostContext,
18 - getRootHostContext,
19 - getChildHostContextForEventComponent,
20 -} from './ReactFiberHostConfig';
16 +import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig';
17 import {createCursor, push, pop} from './ReactFiberStack';
18
19 declare class NoContextT {}
@@ -96,21 +92,6 @@ function pushHostContext(fiber: Fiber): void {
92 push(contextStackCursor, nextContext, fiber);
93 }
94
99 -function pushHostContextForEventComponent(fiber: Fiber): void {
100 - const context: HostContext = requiredContext(contextStackCursor.current);
101 - const nextContext = getChildHostContextForEventComponent(context);
102 -
103 - // Don't push this Fiber's context unless it's unique.
104 - if (context === nextContext) {
105 - return;
106 - }
107 -
108 - // Track the context and the Fiber that provided it.
109 - // This enables us to pop only Fibers that provide unique contexts.
110 - push(contextFiberStackCursor, fiber, fiber);
111 - push(contextStackCursor, nextContext, fiber);
112 -}
113 -
95 function popHostContext(fiber: Fiber): void {
96 // Do not pop unless this Fiber provided the current context.
97 // pushHostContext() only pushes Fibers that provide unique contexts.
@@ -129,5 +110,4 @@ export {
110 popHostContext,
111 pushHostContainer,
112 pushHostContext,
132 - pushHostContextForEventComponent,
113 };
packages/react-reconciler/src/ReactFiberNewContext.js
+2 -1
@@ -377,7 +377,8 @@ export function readContext<T>(
377 currentlyRenderingFiber.dependencies = {
378 expirationTime: NoWork,
379 firstContext: contextItem,
380 - events: null,
380 + listeners: null,
381 + responders: null,
382 };
383 } else {
384 // Append a new context item.
packages/react-reconciler/src/ReactFiberUnwindWork.js
+1 -15
@@ -19,13 +19,9 @@ import {
19 SuspenseComponent,
20 SuspenseListComponent,
21 DehydratedSuspenseComponent,
22 - EventComponent,
22 } from 'shared/ReactWorkTags';
23 import {DidCapture, NoEffect, ShouldCapture} from 'shared/ReactSideEffectTags';
25 -import {
26 - enableSuspenseServerRenderer,
27 - enableFlareAPI,
28 -} from 'shared/ReactFeatureFlags';
24 +import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
25
26 import {popHostContainer, popHostContext} from './ReactFiberHostContext';
27 import {popSuspenseContext} from './ReactFiberSuspenseContext';
@@ -107,11 +103,6 @@ function unwindWork(
103 case ContextProvider:
104 popProvider(workInProgress);
105 return null;
110 - case EventComponent:
111 - if (enableFlareAPI) {
112 - popHostContext(workInProgress);
113 - }
114 - return null;
106 default:
107 return null;
108 }
@@ -153,11 +144,6 @@ function unwindInterruptedWork(interruptedWork: Fiber) {
144 case ContextProvider:
145 popProvider(interruptedWork);
146 break;
156 - case EventComponent:
157 - if (enableFlareAPI) {
158 - popHostContext(interruptedWork);
159 - }
160 - break;
147 default:
148 break;
149 }
packages/react-reconciler/src/__tests__/ReactFiberEvents-test.internal.js deleted
-775
@@ -1,775 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -'use strict';
11 -
12 -let React;
13 -let ReactNoop;
14 -let Scheduler;
15 -let ReactFeatureFlags;
16 -let EventComponent;
17 -let ReactTestRenderer;
18 -let ReactDOM;
19 -let ReactDOMServer;
20 -let ReactTestUtils;
21 -
22 -const noOpResponder = {
23 - displayName: 'TestEventComponent',
24 - targetEventTypes: [],
25 - handleEvent() {},
26 -};
27 -
28 -function createReactEventComponent() {
29 - return React.unstable_createEvent(noOpResponder);
30 -}
31 -
32 -function init() {
33 - jest.resetModules();
34 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
35 - ReactFeatureFlags.enableFlareAPI = true;
36 - React = require('react');
37 - Scheduler = require('scheduler');
38 -}
39 -
40 -function initNoopRenderer() {
41 - init();
42 - ReactNoop = require('react-noop-renderer');
43 -}
44 -
45 -function initTestRenderer() {
46 - init();
47 - ReactTestRenderer = require('react-test-renderer');
48 -}
49 -
50 -function initReactDOM() {
51 - init();
52 - ReactDOM = require('react-dom');
53 - ReactTestUtils = require('react-dom/test-utils');
54 -}
55 -
56 -function initReactDOMServer() {
57 - init();
58 - ReactDOMServer = require('react-dom/server');
59 -}
60 -
61 -// This is a new feature in Fiber so I put it in its own test file. It could
62 -// probably move to one of the other test files once it is official.
63 -describe('ReactFiberEvents', () => {
64 - describe('NoopRenderer', () => {
65 - beforeEach(() => {
66 - initNoopRenderer();
67 - EventComponent = createReactEventComponent();
68 - });
69 -
70 - it('should render a simple event component with a single child', () => {
71 - const Test = () => (
72 - <EventComponent>
73 - <div>Hello world</div>
74 - </EventComponent>
75 - );
76 -
77 - ReactNoop.render(<Test />);
78 - expect(Scheduler).toFlushWithoutYielding();
79 - expect(ReactNoop).toMatchRenderedOutput(<div>Hello world</div>);
80 - });
81 -
82 - it('should warn when an event component has a direct text child', () => {
83 - const Test = () => <EventComponent>Hello world</EventComponent>;
84 -
85 - expect(() => {
86 - ReactNoop.render(<Test />);
87 - expect(Scheduler).toFlushWithoutYielding();
88 - }).toWarnDev(
89 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
90 - 'Wrap the child text "Hello world" in an element.',
91 - );
92 - });
93 -
94 - it('should warn when an event component has a direct text child #2', () => {
95 - const ChildWrapper = () => 'Hello world';
96 - const Test = () => (
97 - <EventComponent>
98 - <ChildWrapper />
99 - </EventComponent>
100 - );
101 -
102 - expect(() => {
103 - ReactNoop.render(<Test />);
104 - expect(Scheduler).toFlushWithoutYielding();
105 - }).toWarnDev(
106 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
107 - 'Wrap the child text "Hello world" in an element.',
108 - );
109 - });
110 -
111 - it('should handle event components correctly with error boundaries', () => {
112 - function ErrorComponent() {
113 - throw new Error('Failed!');
114 - }
115 -
116 - const Test = () => (
117 - <EventComponent>
118 - <span>
119 - <ErrorComponent />
120 - </span>
121 - </EventComponent>
122 - );
123 -
124 - class Wrapper extends React.Component {
125 - state = {
126 - error: null,
127 - };
128 -
129 - componentDidCatch(error) {
130 - this.setState({
131 - error,
132 - });
133 - }
134 -
135 - render() {
136 - if (this.state.error) {
137 - return 'Worked!';
138 - }
139 - return <Test />;
140 - }
141 - }
142 -
143 - ReactNoop.render(<Wrapper />);
144 - expect(Scheduler).toFlushWithoutYielding();
145 - expect(ReactNoop).toMatchRenderedOutput('Worked!');
146 - });
147 -
148 - it('should handle re-renders where there is a bail-out in a parent', () => {
149 - let _updateCounter;
150 -
151 - function Child() {
152 - const [counter, updateCounter] = React.useState(0);
153 -
154 - _updateCounter = updateCounter;
155 -
156 - return (
157 - <div>
158 - <span>Child - {counter}</span>
159 - </div>
160 - );
161 - }
162 -
163 - const Parent = () => (
164 - <EventComponent>
165 - <div>
166 - <Child />
167 - </div>
168 - </EventComponent>
169 - );
170 -
171 - ReactNoop.render(<Parent />);
172 - expect(Scheduler).toFlushWithoutYielding();
173 - expect(ReactNoop).toMatchRenderedOutput(
174 - <div>
175 - <div>
176 - <span>Child - 0</span>
177 - </div>
178 - </div>,
179 - );
180 -
181 - ReactNoop.act(() => {
182 - _updateCounter(counter => counter + 1);
183 - });
184 - expect(Scheduler).toFlushWithoutYielding();
185 -
186 - expect(ReactNoop).toMatchRenderedOutput(
187 - <div>
188 - <div>
189 - <span>Child - 1</span>
190 - </div>
191 - </div>,
192 - );
193 - });
194 -
195 - it('should handle re-renders where there is a bail-out in a parent and an error occurs', () => {
196 - let _updateCounter;
197 -
198 - function Child() {
199 - const [counter, updateCounter] = React.useState(0);
200 -
201 - _updateCounter = updateCounter;
202 -
203 - if (counter === 1) {
204 - return 'Text!';
205 - }
206 -
207 - return (
208 - <div>
209 - <span>Child - {counter}</span>
210 - </div>
211 - );
212 - }
213 -
214 - const Parent = () => (
215 - <EventComponent>
216 - <Child />
217 - </EventComponent>
218 - );
219 -
220 - ReactNoop.render(<Parent />);
221 - expect(Scheduler).toFlushWithoutYielding();
222 - expect(ReactNoop).toMatchRenderedOutput(
223 - <div>
224 - <span>Child - 0</span>
225 - </div>,
226 - );
227 -
228 - expect(() => {
229 - ReactNoop.act(() => {
230 - _updateCounter(counter => counter + 1);
231 - });
232 - expect(Scheduler).toFlushWithoutYielding();
233 - }).toWarnDev(
234 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
235 - 'Wrap the child text "Text!" in an element.',
236 - );
237 - });
238 -
239 - it('should error with a component stack contains the names of the event components and event targets', () => {
240 - let componentStackMessage;
241 -
242 - function ErrorComponent() {
243 - throw new Error('Failed!');
244 - }
245 -
246 - const Test = () => (
247 - <EventComponent>
248 - <span>
249 - <ErrorComponent />
250 - </span>
251 - </EventComponent>
252 - );
253 -
254 - class Wrapper extends React.Component {
255 - state = {
256 - error: null,
257 - };
258 -
259 - componentDidCatch(error, errMessage) {
260 - componentStackMessage = errMessage.componentStack;
261 - this.setState({
262 - error,
263 - });
264 - }
265 -
266 - render() {
267 - if (this.state.error) {
268 - return null;
269 - }
270 - return <Test />;
271 - }
272 - }
273 -
274 - ReactNoop.render(<Wrapper />);
275 - expect(Scheduler).toFlushWithoutYielding();
276 -
277 - expect(componentStackMessage.includes('ErrorComponent')).toBe(true);
278 - expect(componentStackMessage.includes('span')).toBe(true);
279 - expect(componentStackMessage.includes('TestEventComponent')).toBe(true);
280 - expect(componentStackMessage.includes('Test')).toBe(true);
281 - expect(componentStackMessage.includes('Wrapper')).toBe(true);
282 - });
283 - });
284 -
285 - describe('TestRenderer', () => {
286 - beforeEach(() => {
287 - initTestRenderer();
288 - EventComponent = createReactEventComponent();
289 - });
290 -
291 - it('should render a simple event component with a single child', () => {
292 - const Test = () => (
293 - <EventComponent>
294 - <div>Hello world</div>
295 - </EventComponent>
296 - );
297 -
298 - const root = ReactTestRenderer.create(null);
299 - root.update(<Test />);
300 - expect(Scheduler).toFlushWithoutYielding();
301 - expect(root).toMatchRenderedOutput(<div>Hello world</div>);
302 - });
303 -
304 - it('should warn when an event component has a direct text child', () => {
305 - const Test = () => <EventComponent>Hello world</EventComponent>;
306 -
307 - const root = ReactTestRenderer.create(null);
308 - expect(() => {
309 - root.update(<Test />);
310 - expect(Scheduler).toFlushWithoutYielding();
311 - }).toWarnDev(
312 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
313 - 'Wrap the child text "Hello world" in an element.',
314 - );
315 - });
316 -
317 - it('should warn when an event component has a direct text child #2', () => {
318 - const ChildWrapper = () => 'Hello world';
319 - const Test = () => (
320 - <EventComponent>
321 - <ChildWrapper />
322 - </EventComponent>
323 - );
324 -
325 - const root = ReactTestRenderer.create(null);
326 - expect(() => {
327 - root.update(<Test />);
328 - expect(Scheduler).toFlushWithoutYielding();
329 - }).toWarnDev(
330 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
331 - 'Wrap the child text "Hello world" in an element.',
332 - );
333 - });
334 -
335 - it('should handle event components correctly with error boundaries', () => {
336 - function ErrorComponent() {
337 - throw new Error('Failed!');
338 - }
339 -
340 - const Test = () => (
341 - <EventComponent>
342 - <span>
343 - <ErrorComponent />
344 - </span>
345 - </EventComponent>
346 - );
347 -
348 - class Wrapper extends React.Component {
349 - state = {
350 - error: null,
351 - };
352 -
353 - componentDidCatch(error) {
354 - this.setState({
355 - error,
356 - });
357 - }
358 -
359 - render() {
360 - if (this.state.error) {
361 - return 'Worked!';
362 - }
363 - return <Test />;
364 - }
365 - }
366 -
367 - const root = ReactTestRenderer.create(null);
368 - root.update(<Wrapper />);
369 - expect(Scheduler).toFlushWithoutYielding();
370 - expect(root).toMatchRenderedOutput('Worked!');
371 - });
372 -
373 - it('should handle re-renders where there is a bail-out in a parent', () => {
374 - let _updateCounter;
375 -
376 - function Child() {
377 - const [counter, updateCounter] = React.useState(0);
378 -
379 - _updateCounter = updateCounter;
380 -
381 - return (
382 - <div>
383 - <span>Child - {counter}</span>
384 - </div>
385 - );
386 - }
387 -
388 - const Parent = () => (
389 - <EventComponent>
390 - <div>
391 - <Child />
392 - </div>
393 - </EventComponent>
394 - );
395 -
396 - const root = ReactTestRenderer.create(null);
397 - root.update(<Parent />);
398 - expect(Scheduler).toFlushWithoutYielding();
399 - expect(root).toMatchRenderedOutput(
400 - <div>
401 - <div>
402 - <span>Child - 0</span>
403 - </div>
404 - </div>,
405 - );
406 -
407 - ReactTestRenderer.act(() => {
408 - _updateCounter(counter => counter + 1);
409 - });
410 -
411 - expect(root).toMatchRenderedOutput(
412 - <div>
413 - <div>
414 - <span>Child - 1</span>
415 - </div>
416 - </div>,
417 - );
418 - });
419 -
420 - it('should handle re-renders where there is a bail-out in a parent and an error occurs', () => {
421 - let _updateCounter;
422 -
423 - function Child() {
424 - const [counter, updateCounter] = React.useState(0);
425 -
426 - _updateCounter = updateCounter;
427 -
428 - if (counter === 1) {
429 - return 'Text!';
430 - }
431 -
432 - return (
433 - <div>
434 - <span>Child - {counter}</span>
435 - </div>
436 - );
437 - }
438 -
439 - const Parent = () => (
440 - <EventComponent>
441 - <Child />
442 - </EventComponent>
443 - );
444 -
445 - const root = ReactTestRenderer.create(null);
446 - root.update(<Parent />);
447 - expect(Scheduler).toFlushWithoutYielding();
448 - expect(root).toMatchRenderedOutput(
449 - <div>
450 - <span>Child - 0</span>
451 - </div>,
452 - );
453 -
454 - expect(() => {
455 - ReactTestRenderer.act(() => {
456 - _updateCounter(counter => counter + 1);
457 - });
458 - }).toWarnDev(
459 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
460 - 'Wrap the child text "Text!" in an element.',
461 - );
462 - });
463 -
464 - it('should error with a component stack contains the names of the event components and event targets', () => {
465 - let componentStackMessage;
466 -
467 - function ErrorComponent() {
468 - throw new Error('Failed!');
469 - }
470 -
471 - const Test = () => (
472 - <EventComponent>
473 - <span>
474 - <ErrorComponent />
475 - </span>
476 - </EventComponent>
477 - );
478 -
479 - class Wrapper extends React.Component {
480 - state = {
481 - error: null,
482 - };
483 -
484 - componentDidCatch(error, errMessage) {
485 - componentStackMessage = errMessage.componentStack;
486 - this.setState({
487 - error,
488 - });
489 - }
490 -
491 - render() {
492 - if (this.state.error) {
493 - return null;
494 - }
495 - return <Test />;
496 - }
497 - }
498 -
499 - const root = ReactTestRenderer.create(null);
500 - root.update(<Wrapper />);
501 - expect(Scheduler).toFlushWithoutYielding();
502 -
503 - expect(componentStackMessage.includes('ErrorComponent')).toBe(true);
504 - expect(componentStackMessage.includes('span')).toBe(true);
505 - expect(componentStackMessage.includes('TestEventComponent')).toBe(true);
506 - expect(componentStackMessage.includes('Test')).toBe(true);
507 - expect(componentStackMessage.includes('Wrapper')).toBe(true);
508 - });
509 -
510 - it('should handle unwinding event component fibers in concurrent mode', () => {
511 - let resolveThenable;
512 -
513 - const thenable = {
514 - then(resolve) {
515 - resolveThenable = resolve;
516 - },
517 - };
518 -
519 - function Async() {
520 - Scheduler.unstable_yieldValue('Suspend!');
521 - throw thenable;
522 - }
523 -
524 - function Text(props) {
525 - Scheduler.unstable_yieldValue(props.text);
526 - return props.text;
527 - }
528 -
529 - ReactTestRenderer.create(
530 - <React.Suspense fallback={<Text text="Loading..." />}>
531 - <EventComponent>
532 - <div>
533 - <Async />
534 - <Text text="Sibling" />
535 - </div>
536 - </EventComponent>
537 - </React.Suspense>,
538 - {
539 - unstable_isConcurrent: true,
540 - },
541 - );
542 -
543 - expect(Scheduler).toFlushAndYieldThrough(['Suspend!']);
544 - resolveThenable();
545 - });
546 - });
547 -
548 - describe('ReactDOM', () => {
549 - beforeEach(() => {
550 - initReactDOM();
551 - EventComponent = createReactEventComponent();
552 - });
553 -
554 - it('should render a simple event component with a single child', () => {
555 - const Test = () => (
556 - <EventComponent>
557 - <div>Hello world</div>
558 - </EventComponent>
559 - );
560 - const container = document.createElement('div');
561 - ReactDOM.render(<Test />, container);
562 - expect(Scheduler).toFlushWithoutYielding();
563 - expect(container.innerHTML).toBe('<div>Hello world</div>');
564 - });
565 -
566 - it('should warn when an event component has a direct text child', () => {
567 - const Test = () => <EventComponent>Hello world</EventComponent>;
568 -
569 - expect(() => {
570 - const container = document.createElement('div');
571 - ReactDOM.render(<Test />, container);
572 - expect(Scheduler).toFlushWithoutYielding();
573 - }).toWarnDev(
574 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
575 - 'Wrap the child text "Hello world" in an element.',
576 - );
577 - });
578 -
579 - it('should warn when an event component has a direct text child #2', () => {
580 - const ChildWrapper = () => 'Hello world';
581 - const Test = () => (
582 - <EventComponent>
583 - <ChildWrapper />
584 - </EventComponent>
585 - );
586 -
587 - expect(() => {
588 - const container = document.createElement('div');
589 - ReactDOM.render(<Test />, container);
590 - expect(Scheduler).toFlushWithoutYielding();
591 - }).toWarnDev(
592 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
593 - 'Wrap the child text "Hello world" in an element.',
594 - );
595 - });
596 -
597 - it('should handle event components correctly with error boundaries', () => {
598 - function ErrorComponent() {
599 - throw new Error('Failed!');
600 - }
601 -
602 - const Test = () => (
603 - <EventComponent>
604 - <span>
605 - <ErrorComponent />
606 - </span>
607 - </EventComponent>
608 - );
609 -
610 - class Wrapper extends React.Component {
611 - state = {
612 - error: null,
613 - };
614 -
615 - componentDidCatch(error) {
616 - this.setState({
617 - error,
618 - });
619 - }
620 -
621 - render() {
622 - if (this.state.error) {
623 - return 'Worked!';
624 - }
625 - return <Test />;
626 - }
627 - }
628 -
629 - const container = document.createElement('div');
630 - ReactDOM.render(<Wrapper />, container);
631 - expect(Scheduler).toFlushWithoutYielding();
632 - expect(container.innerHTML).toBe('Worked!');
633 - });
634 -
635 - it('should handle re-renders where there is a bail-out in a parent', () => {
636 - let _updateCounter;
637 -
638 - function Child() {
639 - const [counter, updateCounter] = React.useState(0);
640 -
641 - _updateCounter = updateCounter;
642 -
643 - return (
644 - <div>
645 - <span>Child - {counter}</span>
646 - </div>
647 - );
648 - }
649 -
650 - const Parent = () => (
651 - <EventComponent>
652 - <div>
653 - <Child />
654 - </div>
655 - </EventComponent>
656 - );
657 -
658 - const container = document.createElement('div');
659 - ReactDOM.render(<Parent />, container);
660 - expect(container.innerHTML).toBe(
661 - '<div><div><span>Child - 0</span></div></div>',
662 - );
663 -
664 - ReactTestUtils.act(() => {
665 - _updateCounter(counter => counter + 1);
666 - });
667 -
668 - expect(container.innerHTML).toBe(
669 - '<div><div><span>Child - 1</span></div></div>',
670 - );
671 - });
672 -
673 - it('should handle re-renders where there is a bail-out in a parent and an error occurs', () => {
674 - let _updateCounter;
675 -
676 - function Child() {
677 - const [counter, updateCounter] = React.useState(0);
678 -
679 - _updateCounter = updateCounter;
680 -
681 - if (counter === 1) {
682 - return 'Text!';
683 - }
684 -
685 - return (
686 - <div>
687 - <span>Child - {counter}</span>
688 - </div>
689 - );
690 - }
691 -
692 - const Parent = () => (
693 - <EventComponent>
694 - <Child />
695 - </EventComponent>
696 - );
697 -
698 - const container = document.createElement('div');
699 - ReactDOM.render(<Parent />, container);
700 - expect(container.innerHTML).toBe('<div><span>Child - 0</span></div>');
701 -
702 - expect(() => {
703 - ReactTestUtils.act(() => {
704 - _updateCounter(counter => counter + 1);
705 - });
706 - expect(Scheduler).toFlushWithoutYielding();
707 - }).toWarnDev(
708 - 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
709 - 'Wrap the child text "Text!" in an element.',
710 - );
711 - });
712 -
713 - it('should error with a component stack contains the names of the event components and event targets', () => {
714 - let componentStackMessage;
715 -
716 - function ErrorComponent() {
717 - throw new Error('Failed!');
718 - }
719 -
720 - const Test = () => (
721 - <EventComponent>
722 - <span>
723 - <ErrorComponent />
724 - </span>
725 - </EventComponent>
726 - );
727 -
728 - class Wrapper extends React.Component {
729 - state = {
730 - error: null,
731 - };
732 -
733 - componentDidCatch(error, errMessage) {
734 - componentStackMessage = errMessage.componentStack;
735 - this.setState({
736 - error,
737 - });
738 - }
739 -
740 - render() {
741 - if (this.state.error) {
742 - return null;
743 - }
744 - return <Test />;
745 - }
746 - }
747 -
748 - const container = document.createElement('div');
749 - ReactDOM.render(<Wrapper />, container);
750 -
751 - expect(componentStackMessage.includes('ErrorComponent')).toBe(true);
752 - expect(componentStackMessage.includes('span')).toBe(true);
753 - expect(componentStackMessage.includes('TestEventComponent')).toBe(true);
754 - expect(componentStackMessage.includes('Test')).toBe(true);
755 - expect(componentStackMessage.includes('Wrapper')).toBe(true);
756 - });
757 - });
758 -
759 - describe('ReactDOMServer', () => {
760 - beforeEach(() => {
761 - initReactDOMServer();
762 - EventComponent = createReactEventComponent();
763 - });
764 -
765 - it('should render a simple event component with a single child', () => {
766 - const Test = () => (
767 - <EventComponent>
768 - <div>Hello world</div>
769 - </EventComponent>
770 - );
771 - const output = ReactDOMServer.renderToString(<Test />);
772 - expect(output).toBe('<div>Hello world</div>');
773 - });
774 - });
775 -});
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
-3
@@ -33,9 +33,6 @@ describe('ReactFiberHostContext', () => {
33 getChildHostContext: function() {
34 return null;
35 },
36 - getChildHostContextForEventComponent: function() {
37 - return null;
38 - },
36 shouldSetTextContent: function() {
37 return false;
38 },
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+2 -6
@@ -42,8 +42,6 @@ export type EventResponder = any;
42 export const getPublicInstance = $$$hostConfig.getPublicInstance;
43 export const getRootHostContext = $$$hostConfig.getRootHostContext;
44 export const getChildHostContext = $$$hostConfig.getChildHostContext;
45 -export const getChildHostContextForEventComponent =
46 - $$$hostConfig.getChildHostContextForEventComponent;
45 export const prepareForCommit = $$$hostConfig.prepareForCommit;
46 export const resetAfterCommit = $$$hostConfig.resetAfterCommit;
47 export const createInstance = $$$hostConfig.createInstance;
@@ -63,8 +61,8 @@ export const warnsIfNotActing = $$$hostConfig.warnsIfNotActing;
61 export const supportsMutation = $$$hostConfig.supportsMutation;
62 export const supportsPersistence = $$$hostConfig.supportsPersistence;
63 export const supportsHydration = $$$hostConfig.supportsHydration;
66 -export const mountEventComponent = $$$hostConfig.mountEventComponent;
67 -export const updateEventComponent = $$$hostConfig.updateEventComponent;
64 +export const mountResponderInstance = $$$hostConfig.mountResponderInstance;
65 +export const unmountResponderInstance = $$$hostConfig.unmountResponderInstance;
66 export const getFundamentalComponentInstance =
67 $$$hostConfig.getFundamentalComponentInstance;
68 export const mountFundamentalComponent =
@@ -90,8 +88,6 @@ export const hideInstance = $$$hostConfig.hideInstance;
88 export const hideTextInstance = $$$hostConfig.hideTextInstance;
89 export const unhideInstance = $$$hostConfig.unhideInstance;
90 export const unhideTextInstance = $$$hostConfig.unhideTextInstance;
93 -export const unmountEventComponent = $$$hostConfig.unmountEventComponent;
94 -export const commitEventTarget = $$$hostConfig.commitEventTarget;
91 export const updateFundamentalComponent =
92 $$$hostConfig.updateFundamentalComponent;
93 export const unmountFundamentalComponent =
packages/react-test-renderer/src/ReactShallowRenderer.js
+1 -1
@@ -381,7 +381,7 @@ class ReactShallowRenderer {
381 useReducer,
382 useRef,
383 useState,
384 - useEvent: noOp,
384 + useListener: noOp,
385 };
386 }
387
packages/react-test-renderer/src/ReactTestHostConfig.js
+14 -23
@@ -10,7 +10,8 @@
10 import warning from 'shared/warning';
11
12 import type {
13 - ReactEventComponentInstance,
13 + ReactEventResponder,
14 + ReactEventResponderInstance,
15 ReactFundamentalComponentInstance,
16 } from 'shared/ReactTypes';
17
@@ -124,15 +125,6 @@ export function getChildHostContext(
125 return NO_CONTEXT;
126 }
127
127 -export function getChildHostContextForEventComponent(
128 - parentHostContext: HostContext,
129 -): HostContext {
130 - if (__DEV__ && enableFlareAPI) {
131 - return EVENT_COMPONENT_CONTEXT;
132 - }
133 - return NO_CONTEXT;
134 -}
135 -
128 export function prepareForCommit(containerInfo: Container): void {
129 // noop
130 }
@@ -288,22 +280,21 @@ export function unhideTextInstance(
280 textInstance.isHidden = false;
281 }
282
291 -export function mountEventComponent(
292 - eventComponentInstance: ReactEventComponentInstance<any, any>,
293 -): void {
294 - // noop
295 -}
296 -
297 -export function updateEventComponent(
298 - eventComponentInstance: ReactEventComponentInstance<any, any>,
299 -): void {
300 - // noop
283 +export function mountResponderInstance(
284 + responder: ReactEventResponder<any, any>,
285 + responderInstance: ReactEventResponderInstance<any, any>,
286 + props: Object,
287 + state: Object,
288 + instance: Instance,
289 + rootContainerInstance: Container,
290 +) {
291 + throw new Error('Not yet implemented.');
292 }
293
303 -export function unmountEventComponent(
304 - eventComponentInstance: ReactEventComponentInstance<any, any>,
294 +export function unmountResponderInstance(
295 + responderInstance: ReactEventResponderInstance<any, any>,
296 ): void {
306 - // noop
297 + throw new Error('Not yet implemented.');
298 }
299
300 export function getFundamentalComponentInstance(fundamentalInstance): Instance {
packages/react/src/React.js
+4 -4
@@ -39,7 +39,7 @@ import {
39 useReducer,
40 useRef,
41 useState,
42 - useEvent,
42 + useListener,
43 } from './ReactHooks';
44 import {withSuspenseConfig} from './ReactBatchConfig';
45 import {
@@ -51,8 +51,8 @@ import {
51 jsxWithValidationDynamic,
52 } from './ReactElementValidator';
53 import ReactSharedInternals from './ReactSharedInternals';
54 -import createEvent from 'shared/createEventComponent';
54 import createFundamental from 'shared/createFundamentalComponent';
55 +import createResponder from 'shared/createEventResponder';
56 import {
57 enableJSXTransformAPI,
58 enableFlareAPI,
@@ -106,8 +106,8 @@ const React = {
106 };
107
108 if (enableFlareAPI) {
109 - React.unstable_createEvent = createEvent;
110 - React.unstable_useEvent = useEvent;
109 + React.unstable_useListener = useListener;
110 + React.unstable_createResponder = createResponder;
111 }
112
113 if (enableFundamentalAPI) {
packages/react/src/ReactHooks.js
+16 -5
@@ -7,9 +7,10 @@
7 * @flow
8 */
9
10 -import type {ReactEventComponent, ReactContext} from 'shared/ReactTypes';
10 +import type {ReactContext, ReactEventResponder} from 'shared/ReactTypes';
11 import invariant from 'shared/invariant';
12 import warning from 'shared/warning';
13 +import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
14
15 import ReactCurrentDispatcher from './ReactCurrentDispatcher';
16
@@ -138,10 +139,20 @@ export function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
139
140 export const emptyObject = {};
141
141 -export function useEvent<E, C>(
142 - eventComponent: ReactEventComponent<E, C>,
143 - props: null | Object,
142 +export function useListener(
143 + responder: ReactEventResponder<any, any>,
144 + hookProps: ?Object,
145 ) {
146 const dispatcher = resolveDispatcher();
146 - return dispatcher.useEvent(eventComponent, props || emptyObject);
147 + if (__DEV__) {
148 + if (responder == null || responder.$$typeof !== REACT_RESPONDER_TYPE) {
149 + warning(
150 + false,
151 + 'useListener: invalid first argument. Expected an event responder, but instead got %s',
152 + responder,
153 + );
154 + return;
155 + }
156 + }
157 + return dispatcher.useListener(responder, hookProps || emptyObject);
158 }
packages/shared/ReactDOMTypes.js
+7 -10
@@ -10,7 +10,7 @@
10 import type {
11 ReactFundamentalComponentInstance,
12 ReactEventResponder,
13 - ReactEventComponentInstance,
13 + ReactEventResponderInstance,
14 EventPriority,
15 } from 'shared/ReactTypes';
16
@@ -40,7 +40,7 @@ export type ReactDOMEventResponder = ReactEventResponder<
40 ReactDOMResponderContext,
41 >;
42
43 -export type ReactDOMEventComponentInstance = ReactEventComponentInstance<
43 +export type ReactDOMEventResponderInstance = ReactEventResponderInstance<
44 ReactDOMResponderEvent,
45 ReactDOMResponderContext,
46 >;
@@ -52,16 +52,16 @@ export type ReactDOMFundamentalComponentInstance = ReactFundamentalComponentInst
52
53 export type ReactDOMResponderContext = {
54 dispatchEvent: (
55 - eventObject: Object,
56 - listener: (Object) => void,
55 + eventProp: string,
56 + eventValue: any,
57 eventPriority: EventPriority,
58 ) => void,
59 isTargetWithinNode: (
60 childTarget: Element | Document,
61 parentTarget: Element | Document,
62 ) => boolean,
63 - isTargetWithinEventComponent: (Element | Document) => boolean,
64 - isTargetWithinEventResponderScope: (Element | Document) => boolean,
63 + isTargetWithinResponder: (Element | Document) => boolean,
64 + isTargetWithinResponderScope: (Element | Document) => boolean,
65 addRootEventTypes: (rootEventTypes: Array<string>) => void,
66 removeRootEventTypes: (rootEventTypes: Array<string>) => void,
67 hasOwnership: () => boolean,
@@ -69,17 +69,14 @@ export type ReactDOMResponderContext = {
69 releaseOwnership: () => boolean,
70 setTimeout: (func: () => void, timeout: number) => number,
71 clearTimeout: (timerId: number) => void,
72 - getFocusableElementsInScope(): Array<HTMLElement>,
72 + getFocusableElementsInScope(deep: boolean): Array<HTMLElement>,
73 getActiveDocument(): Document,
74 objectAssign: Function,
75 getTimeStamp: () => number,
76 isTargetWithinHostComponent: (
77 target: Element | Document,
78 elementType: string,
79 - deep: boolean,
79 ) => boolean,
81 - continueLocalPropagation(): void,
82 - isRespondingToHook(): boolean,
80 // Used for controller components
81 enqueueStateRestore(Element | Document): void,
82 };
packages/shared/ReactSymbols.js
+3 -3
@@ -51,11 +51,11 @@ export const REACT_SUSPENSE_LIST_TYPE = hasSymbol
51 : 0xead8;
52 export const REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
53 export const REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;
54 -export const REACT_EVENT_COMPONENT_TYPE = hasSymbol
55 - ? Symbol.for('react.event_component')
56 - : 0xead5;
54 export const REACT_FUNDAMENTAL_TYPE = hasSymbol
55 ? Symbol.for('react.fundamental')
56 + : 0xead5;
57 +export const REACT_RESPONDER_TYPE = hasSymbol
58 + ? Symbol.for('react.responder')
59 : 0xead6;
60
61 const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
packages/shared/ReactTypes.js
+19 -13
@@ -80,26 +80,32 @@ export type RefObject = {|
80 current: any,
81 |};
82
83 -export type ReactEventComponentInstance<E, C> = {|
84 - currentFiber: mixed,
85 - isHook: boolean,
83 +export type ReactEventResponderInstance<E, C> = {|
84 + fiber: Object,
85 props: Object,
86 responder: ReactEventResponder<E, C>,
87 rootEventTypes: null | Set<string>,
89 - rootInstance: null | mixed,
88 state: Object,
89 + target: mixed,
90 |};
91
92 export type ReactEventResponder<E, C> = {
93 + $$typeof: Symbol | number,
94 displayName: string,
95 - targetEventTypes?: Array<string>,
96 - rootEventTypes?: Array<string>,
97 - getInitialState?: (props: Object) => Object,
98 - onEvent?: (event: E, context: C, props: Object, state: Object) => void,
99 - onRootEvent?: (event: E, context: C, props: Object, state: Object) => void,
100 - onMount?: (context: C, props: Object, state: Object) => void,
101 - onUnmount?: (context: C, props: Object, state: Object) => void,
102 - onOwnershipChange?: (context: C, props: Object, state: Object) => void,
95 + targetEventTypes: null | Array<string>,
96 + rootEventTypes: null | Array<string>,
97 + getInitialState: null | ((props: Object) => Object),
98 + onEvent:
99 + | null
100 + | ((event: E, context: C, props: Object, state: Object) => void),
101 + onRootEvent:
102 + | null
103 + | ((event: E, context: C, props: Object, state: Object) => void),
104 + onMount: null | ((context: C, props: Object, state: Object) => void),
105 + onUnmount: null | ((context: C, props: Object, state: Object) => void),
106 + onOwnershipChange:
107 + | null
108 + | ((context: C, props: Object, state: Object) => void),
109 };
110
111 export type ReactEventComponent<E, C> = {|
@@ -107,7 +113,7 @@ export type ReactEventComponent<E, C> = {|
113 responder: ReactEventResponder<E, C>,
114 |};
115
110 -export opaque type EventPriority = 0 | 1 | 2;
116 +export type EventPriority = 0 | 1 | 2;
117
118 export const DiscreteEvent: EventPriority = 0;
119 export const UserBlockingEvent: EventPriority = 1;
packages/shared/ReactWorkTags.js
+2 -4
@@ -28,8 +28,7 @@ export type WorkTag =
28 | 17
29 | 18
30 | 19
31 - | 20
32 - | 21;
31 + | 20;
32
33 export const FunctionComponent = 0;
34 export const ClassComponent = 1;
@@ -50,6 +49,5 @@ export const SimpleMemoComponent = 15;
49 export const LazyComponent = 16;
50 export const IncompleteClassComponent = 17;
51 export const DehydratedSuspenseComponent = 18;
53 -export const EventComponent = 19;
52 +export const SuspenseListComponent = 19;
53 export const FundamentalComponent = 20;
55 -export const SuspenseListComponent = 21;
packages/shared/createEventComponent.js deleted
-30
@@ -1,30 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - * @flow
7 - */
8 -
9 -import type {ReactEventResponder, ReactEventComponent} from 'shared/ReactTypes';
10 -import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
11 -import {hasBadMapPolyfill} from './hasBadMapPolyfill';
12 -
13 -export default function createEventComponent<E, C>(
14 - responder: ReactEventResponder<E, C>,
15 -): ReactEventComponent<E, C> {
16 - // We use responder as a Map key later on. When we have a bad
17 - // polyfill, then we can't use it as a key as the polyfill tries
18 - // to add a property to the object.
19 - if (__DEV__ && !hasBadMapPolyfill) {
20 - Object.freeze(responder);
21 - }
22 - const eventComponent = {
23 - $$typeof: REACT_EVENT_COMPONENT_TYPE,
24 - responder,
25 - };
26 - if (__DEV__) {
27 - Object.freeze(eventComponent);
28 - }
29 - return eventComponent;
30 -}
packages/shared/createEventResponder.js new
+42
@@ -0,0 +1,42 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + * @flow
7 + */
8 +
9 +import type {ReactEventResponder} from 'shared/ReactTypes';
10 +import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
11 +
12 +export default function createEventResponder<E, C>(
13 + displayName: string,
14 + responderConfig: Object,
15 +): ReactEventResponder<E, C> {
16 + const {
17 + getInitialState,
18 + onEvent,
19 + onMount,
20 + onUnmount,
21 + onOwnershipChange,
22 + onRootEvent,
23 + rootEventTypes,
24 + targetEventTypes,
25 + } = responderConfig;
26 + const eventResponder = {
27 + $$typeof: REACT_RESPONDER_TYPE,
28 + displayName,
29 + getInitialState: getInitialState || null,
30 + onEvent: onEvent || null,
31 + onMount: onMount || null,
32 + onOwnershipChange: onOwnershipChange || null,
33 + onRootEvent: onRootEvent || null,
34 + onUnmount: onUnmount || null,
35 + rootEventTypes: rootEventTypes || null,
36 + targetEventTypes: targetEventTypes || null,
37 + };
38 + if (__DEV__) {
39 + Object.freeze(eventResponder);
40 + }
41 + return eventResponder;
42 +}
packages/shared/getComponentName.js
-9
@@ -22,12 +22,9 @@ import {
22 REACT_SUSPENSE_TYPE,
23 REACT_SUSPENSE_LIST_TYPE,
24 REACT_LAZY_TYPE,
25 - REACT_EVENT_COMPONENT_TYPE,
25 } from 'shared/ReactSymbols';
26 import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
27
29 -import {enableFlareAPI} from './ReactFeatureFlags';
30 -
28 function getWrappedName(
29 outerType: mixed,
30 innerType: any,
@@ -92,12 +89,6 @@ function getComponentName(type: mixed): string | null {
89 }
90 break;
91 }
95 - case REACT_EVENT_COMPONENT_TYPE: {
96 - if (enableFlareAPI) {
97 - return (type: any).responder.displayName;
98 - }
99 - break;
100 - }
92 }
93 }
94 return null;
packages/shared/isValidElementType.js
+3 -3
@@ -19,8 +19,8 @@ import {
19 REACT_SUSPENSE_LIST_TYPE,
20 REACT_MEMO_TYPE,
21 REACT_LAZY_TYPE,
22 - REACT_EVENT_COMPONENT_TYPE,
22 REACT_FUNDAMENTAL_TYPE,
23 + REACT_RESPONDER_TYPE,
24 } from 'shared/ReactSymbols';
25
26 export default function isValidElementType(type: mixed) {
@@ -41,7 +41,7 @@ export default function isValidElementType(type: mixed) {
41 type.$$typeof === REACT_PROVIDER_TYPE ||
42 type.$$typeof === REACT_CONTEXT_TYPE ||
43 type.$$typeof === REACT_FORWARD_REF_TYPE ||
44 - type.$$typeof === REACT_EVENT_COMPONENT_TYPE ||
45 - type.$$typeof === REACT_FUNDAMENTAL_TYPE))
44 + type.$$typeof === REACT_FUNDAMENTAL_TYPE ||
45 + type.$$typeof === REACT_RESPONDER_TYPE))
46 );
47 }
scripts/error-codes/codes.json
+3 -1
@@ -335,5 +335,7 @@
335 "334": "accumulate(...): Accumulated items must not be null or undefined.",
336 "335": "ReactDOMServer does not yet support the event API.",
337 "336": "The \"%s\" event responder cannot be used via the \"useEvent\" hook.",
338 - "337": "ReactDOMServer does not yet support the fundamental API."
338 + "337": "An invalid event responder was provided to host component",
339 + "338": "ReactDOMServer does not yet support the fundamental API.",
340 + "339": "An invalid value was used as an event responder. Expect one or many event responders created via React.unstable_createResponer()."
341 }