[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
}