Experimental event API: loosen EventTarget constraints and warnings (#15292)
* Remove warning for event targets being direct children of event component * Addressed feedback and added more test coverage + warnings
Dominic Gannaway committed
Apr 2, 2019 at 19:49 UTC
6a1e6b2f78da3a56aa497902951c6e9ce654eafc
14 files changed
+577
-309
packages/react-art/src/ReactARTHostConfig.js
+6
-1
@@ -340,7 +340,11 @@ export function getChildHostContext() {
340
return NO_CONTEXT;
341
}
342
343
-export function getChildHostContextForEvent() {
343
+export function getChildHostContextForEventComponent() {
344
+ return NO_CONTEXT;
345
+}
346
+
347
+export function getChildHostContextForEventTarget() {
348
return NO_CONTEXT;
349
}
350
@@ -446,6 +450,7 @@ export function handleEventComponent(
450
export function handleEventTarget(
451
type: Symbol | number,
452
props: Props,
453
+ parentInstance: Container,
454
internalInstanceHandle: Object,
455
) {
456
// TODO: add handleEventTarget implementation
packages/react-dom/src/client/ReactDOMHostConfig.js
+56
-45
@@ -45,12 +45,7 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
45
46
import type {DOMContainer} from './ReactDOM';
47
import type {ReactEventResponder} from 'shared/ReactTypes';
48
-import {
49
- REACT_EVENT_COMPONENT_TYPE,
50
- REACT_EVENT_TARGET_TYPE,
51
- REACT_EVENT_TARGET_TOUCH_HIT,
52
-} from 'shared/ReactSymbols';
53
-import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
48
+import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
49
50
export type Type = string;
51
export type Props = {
@@ -75,6 +70,7 @@ type HostContextDev = {
70
eventData: null | {|
71
isEventComponent?: boolean,
72
isEventTarget?: boolean,
73
+ eventTargetType?: null | Symbol | number,
74
|},
75
};
76
type HostContextProd = string;
@@ -180,36 +176,46 @@ export function getChildHostContext(
176
return getChildNamespace(parentNamespace, type);
177
}
178
183
-export function getChildHostContextForEvent(
179
+export function getChildHostContextForEventComponent(
180
parentHostContext: HostContext,
185
- type: Symbol | number,
181
): HostContext {
182
if (__DEV__) {
183
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
184
const {namespace, ancestorInfo} = parentHostContextDev;
190
- let eventData = null;
185
+ warning(
186
+ parentHostContextDev.eventData === null ||
187
+ !parentHostContextDev.eventData.isEventTarget,
188
+ 'validateDOMNesting: React event targets must not have event components as children.',
189
+ );
190
+ const eventData = {
191
+ isEventComponent: true,
192
+ isEventTarget: false,
193
+ eventTargetType: null,
194
+ };
195
+ return {namespace, ancestorInfo, eventData};
196
+ }
197
+ return parentHostContext;
198
+}
199
192
- if (type === REACT_EVENT_COMPONENT_TYPE) {
193
- warning(
194
- parentHostContextDev.eventData === null ||
195
- !parentHostContextDev.eventData.isEventTarget,
196
- 'validateDOMNesting: React event targets must not have event components as children.',
197
- );
198
- eventData = {
199
- isEventComponent: true,
200
- isEventTarget: false,
201
- };
202
- } else if (type === REACT_EVENT_TARGET_TYPE) {
203
- warning(
204
- parentHostContextDev.eventData !== null &&
205
- parentHostContextDev.eventData.isEventComponent,
206
- 'validateDOMNesting: React event targets must be direct children of event components.',
207
- );
208
- eventData = {
209
- isEventComponent: false,
210
- isEventTarget: true,
211
- };
212
- }
200
+export function getChildHostContextForEventTarget(
201
+ parentHostContext: HostContext,
202
+ type: Symbol | number,
203
+): HostContext {
204
+ if (__DEV__) {
205
+ const parentHostContextDev = ((parentHostContext: any): HostContextDev);
206
+ const {namespace, ancestorInfo} = parentHostContextDev;
207
+ warning(
208
+ parentHostContextDev.eventData === null ||
209
+ !parentHostContextDev.eventData.isEventComponent ||
210
+ type !== REACT_EVENT_TARGET_TOUCH_HIT,
211
+ 'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
212
+ 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
213
+ );
214
+ const eventData = {
215
+ isEventComponent: false,
216
+ isEventTarget: true,
217
+ eventTargetType: type,
218
+ };
219
return {namespace, ancestorInfo, eventData};
220
}
221
return parentHostContext;
@@ -243,6 +249,16 @@ export function createInstance(
249
if (__DEV__) {
250
// TODO: take namespace into account when validating.
251
const hostContextDev = ((hostContext: any): HostContextDev);
252
+ if (enableEventAPI) {
253
+ const eventData = hostContextDev.eventData;
254
+ if (eventData !== null) {
255
+ warning(
256
+ !eventData.isEventTarget ||
257
+ eventData.eventTargetType !== REACT_EVENT_TARGET_TOUCH_HIT,
258
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
259
+ );
260
+ }
261
+ }
262
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
263
if (
264
typeof props.children === 'string' ||
@@ -349,6 +365,12 @@ export function createTextInstance(
365
if (enableEventAPI) {
366
const eventData = hostContextDev.eventData;
367
if (eventData !== null) {
368
+ warning(
369
+ eventData === null ||
370
+ !eventData.isEventTarget ||
371
+ eventData.eventTargetType !== REACT_EVENT_TARGET_TOUCH_HIT,
372
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
373
+ );
374
warning(
375
!eventData.isEventComponent,
376
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@@ -356,7 +378,8 @@ export function createTextInstance(
378
text,
379
);
380
warning(
359
- !eventData.isEventTarget,
381
+ !eventData.isEventTarget ||
382
+ eventData.eventTargetType === REACT_EVENT_TARGET_TOUCH_HIT,
383
'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
384
'Wrap the child text "%s" in an element.',
385
text,
@@ -879,25 +902,13 @@ export function handleEventComponent(
902
export function handleEventTarget(
903
type: Symbol | number,
904
props: Props,
905
+ parentInstance: Container,
906
internalInstanceHandle: Object,
907
): void {
908
if (enableEventAPI) {
909
// Touch target hit slop handling
910
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
887
- // Validates that there is a single element
888
- const element = getElementFromTouchHitTarget(internalInstanceHandle);
889
- if (element !== null) {
890
- // We update the event target state node to be that of the element.
891
- // We can then diff this entry to determine if we need to add the
892
- // hit slop element, or change the dimensions of the hit slop.
893
- const lastElement = internalInstanceHandle.stateNode;
894
- if (lastElement !== element) {
895
- internalInstanceHandle.stateNode = element;
896
- // TODO: Create the hit slop element and attach it to the element
897
- } else {
898
- // TODO: Diff the left, top, right, bottom props
899
- }
900
- }
911
+ // TODO
912
}
913
}
914
}
packages/react-events/src/__tests__/TouchHitTarget-test.internal.js
new
+322
@@ -0,0 +1,322 @@
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 ReactSymbols;
20
+let ReactEvents;
21
+let TouchHitTarget;
22
+
23
+const noOpResponder = {
24
+ targetEventTypes: [],
25
+ handleEvent() {},
26
+};
27
+
28
+function createReactEventComponent() {
29
+ return {
30
+ $$typeof: ReactSymbols.REACT_EVENT_COMPONENT_TYPE,
31
+ props: null,
32
+ responder: noOpResponder,
33
+ };
34
+}
35
+
36
+function init() {
37
+ jest.resetModules();
38
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
39
+ ReactFeatureFlags.enableEventAPI = true;
40
+ React = require('react');
41
+ Scheduler = require('scheduler');
42
+ ReactSymbols = require('shared/ReactSymbols');
43
+ ReactEvents = require('react-events');
44
+}
45
+
46
+function initNoopRenderer() {
47
+ init();
48
+ ReactNoop = require('react-noop-renderer');
49
+}
50
+
51
+function initTestRenderer() {
52
+ init();
53
+ ReactTestRenderer = require('react-test-renderer');
54
+}
55
+
56
+function initReactDOM() {
57
+ init();
58
+ ReactDOM = require('react-dom');
59
+}
60
+
61
+describe('TouchHitTarget', () => {
62
+ describe('NoopRenderer', () => {
63
+ beforeEach(() => {
64
+ initNoopRenderer();
65
+ EventComponent = createReactEventComponent();
66
+ TouchHitTarget = ReactEvents.TouchHitTarget;
67
+ });
68
+
69
+ it('should not warn when a TouchHitTarget is used correctly', () => {
70
+ const Test = () => (
71
+ <EventComponent>
72
+ <div>
73
+ <TouchHitTarget />
74
+ </div>
75
+ </EventComponent>
76
+ );
77
+
78
+ ReactNoop.render(<Test />);
79
+ expect(Scheduler).toFlushWithoutYielding();
80
+ expect(ReactNoop).toMatchRenderedOutput(<div />);
81
+ });
82
+
83
+ it('should warn when a TouchHitTarget has children', () => {
84
+ const Test = () => (
85
+ <EventComponent>
86
+ <div>
87
+ <TouchHitTarget>
88
+ <span>Child 1</span>
89
+ </TouchHitTarget>
90
+ </div>
91
+ </EventComponent>
92
+ );
93
+
94
+ expect(() => {
95
+ ReactNoop.render(<Test />);
96
+ expect(Scheduler).toFlushWithoutYielding();
97
+ }).toWarnDev(
98
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
99
+ );
100
+
101
+ const Test2 = () => (
102
+ <EventComponent>
103
+ <div>
104
+ <TouchHitTarget>Child 1</TouchHitTarget>
105
+ </div>
106
+ </EventComponent>
107
+ );
108
+
109
+ expect(() => {
110
+ ReactNoop.render(<Test2 />);
111
+ expect(Scheduler).toFlushWithoutYielding();
112
+ }).toWarnDev(
113
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
114
+ );
115
+
116
+ // Should render without warnings
117
+ const Test3 = () => (
118
+ <EventComponent>
119
+ <div>
120
+ <TouchHitTarget />
121
+ </div>
122
+ </EventComponent>
123
+ );
124
+
125
+ ReactNoop.render(<Test3 />);
126
+ expect(Scheduler).toFlushWithoutYielding();
127
+ expect(ReactNoop).toMatchRenderedOutput(<div />);
128
+ });
129
+
130
+ it('should warn when a TouchHitTarget is a direct child of an event component', () => {
131
+ const Test = () => (
132
+ <EventComponent>
133
+ <TouchHitTarget />
134
+ </EventComponent>
135
+ );
136
+
137
+ expect(() => {
138
+ ReactNoop.render(<Test />);
139
+ expect(Scheduler).toFlushWithoutYielding();
140
+ }).toWarnDev(
141
+ 'Warning: validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
142
+ 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
143
+ );
144
+ });
145
+ });
146
+
147
+ describe('TestRenderer', () => {
148
+ beforeEach(() => {
149
+ initTestRenderer();
150
+ EventComponent = createReactEventComponent();
151
+ TouchHitTarget = ReactEvents.TouchHitTarget;
152
+ });
153
+
154
+ it('should not warn when a TouchHitTarget is used correctly', () => {
155
+ const Test = () => (
156
+ <EventComponent>
157
+ <div>
158
+ <TouchHitTarget />
159
+ </div>
160
+ </EventComponent>
161
+ );
162
+
163
+ const root = ReactTestRenderer.create(null);
164
+ root.update(<Test />);
165
+ expect(Scheduler).toFlushWithoutYielding();
166
+ expect(root).toMatchRenderedOutput(<div />);
167
+ });
168
+
169
+ it('should warn when a TouchHitTarget has children', () => {
170
+ const Test = () => (
171
+ <EventComponent>
172
+ <div>
173
+ <TouchHitTarget>
174
+ <span>Child 1</span>
175
+ </TouchHitTarget>
176
+ </div>
177
+ </EventComponent>
178
+ );
179
+
180
+ const root = ReactTestRenderer.create(null);
181
+ expect(() => {
182
+ root.update(<Test />);
183
+ expect(Scheduler).toFlushWithoutYielding();
184
+ }).toWarnDev(
185
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
186
+ );
187
+
188
+ const Test2 = () => (
189
+ <EventComponent>
190
+ <div>
191
+ <TouchHitTarget>Child 1</TouchHitTarget>
192
+ </div>
193
+ </EventComponent>
194
+ );
195
+
196
+ expect(() => {
197
+ root.update(<Test2 />);
198
+ expect(Scheduler).toFlushWithoutYielding();
199
+ }).toWarnDev(
200
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
201
+ );
202
+
203
+ // Should render without warnings
204
+ const Test3 = () => (
205
+ <EventComponent>
206
+ <div>
207
+ <TouchHitTarget />
208
+ </div>
209
+ </EventComponent>
210
+ );
211
+
212
+ root.update(<Test3 />);
213
+ expect(Scheduler).toFlushWithoutYielding();
214
+ expect(root).toMatchRenderedOutput(<div />);
215
+ });
216
+
217
+ it('should warn when a TouchHitTarget is a direct child of an event component', () => {
218
+ const Test = () => (
219
+ <EventComponent>
220
+ <TouchHitTarget />
221
+ </EventComponent>
222
+ );
223
+
224
+ const root = ReactTestRenderer.create(null);
225
+ expect(() => {
226
+ root.update(<Test />);
227
+ expect(Scheduler).toFlushWithoutYielding();
228
+ }).toWarnDev(
229
+ 'Warning: validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
230
+ 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
231
+ );
232
+ });
233
+ });
234
+
235
+ describe('ReactDOM', () => {
236
+ beforeEach(() => {
237
+ initReactDOM();
238
+ EventComponent = createReactEventComponent();
239
+ TouchHitTarget = ReactEvents.TouchHitTarget;
240
+ });
241
+
242
+ it('should not warn when a TouchHitTarget is used correctly', () => {
243
+ const Test = () => (
244
+ <EventComponent>
245
+ <div>
246
+ <TouchHitTarget />
247
+ </div>
248
+ </EventComponent>
249
+ );
250
+
251
+ const container = document.createElement('div');
252
+ ReactDOM.render(<Test />, container);
253
+ expect(Scheduler).toFlushWithoutYielding();
254
+ expect(container.innerHTML).toBe('<div></div>');
255
+ });
256
+
257
+ it('should warn when a TouchHitTarget has children', () => {
258
+ const Test = () => (
259
+ <EventComponent>
260
+ <div>
261
+ <TouchHitTarget>
262
+ <span>Child 1</span>
263
+ </TouchHitTarget>
264
+ </div>
265
+ </EventComponent>
266
+ );
267
+
268
+ const container = document.createElement('div');
269
+ expect(() => {
270
+ ReactDOM.render(<Test />, container);
271
+ expect(Scheduler).toFlushWithoutYielding();
272
+ }).toWarnDev(
273
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
274
+ );
275
+
276
+ const Test2 = () => (
277
+ <EventComponent>
278
+ <div>
279
+ <TouchHitTarget>Child 1</TouchHitTarget>
280
+ </div>
281
+ </EventComponent>
282
+ );
283
+
284
+ expect(() => {
285
+ ReactDOM.render(<Test2 />, container);
286
+ expect(Scheduler).toFlushWithoutYielding();
287
+ }).toWarnDev(
288
+ 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
289
+ );
290
+
291
+ // Should render without warnings
292
+ const Test3 = () => (
293
+ <EventComponent>
294
+ <div>
295
+ <TouchHitTarget />
296
+ </div>
297
+ </EventComponent>
298
+ );
299
+
300
+ ReactDOM.render(<Test3 />, container);
301
+ expect(Scheduler).toFlushWithoutYielding();
302
+ expect(container.innerHTML).toBe('<div></div>');
303
+ });
304
+
305
+ it('should warn when a TouchHitTarget is a direct child of an event component', () => {
306
+ const Test = () => (
307
+ <EventComponent>
308
+ <TouchHitTarget />
309
+ </EventComponent>
310
+ );
311
+
312
+ const container = document.createElement('div');
313
+ expect(() => {
314
+ ReactDOM.render(<Test />, container);
315
+ expect(Scheduler).toFlushWithoutYielding();
316
+ }).toWarnDev(
317
+ 'Warning: validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
318
+ 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
319
+ );
320
+ });
321
+ });
322
+});
packages/react-native-renderer/src/ReactFabricHostConfig.js
+10
-2
@@ -283,11 +283,18 @@ export function getChildHostContext(
283
}
284
}
285
286
-export function getChildHostContextForEvent(
286
+export function getChildHostContextForEventComponent(
287
+ parentHostContext: HostContext,
288
+) {
289
+ // TODO: add getChildHostContextForEventComponent implementation
290
+ return parentHostContext;
291
+}
292
+
293
+export function getChildHostContextForEventTarget(
294
parentHostContext: HostContext,
295
type: Symbol | number,
296
) {
290
- // TODO: add getChildHostContextForEvent implementation
297
+ // TODO: add getChildHostContextForEventTarget implementation
298
return parentHostContext;
299
}
300
@@ -438,6 +445,7 @@ export function handleEventComponent(
445
export function handleEventTarget(
446
type: Symbol | number,
447
props: Props,
448
+ parentInstance: Container,
449
internalInstanceHandle: Object,
450
) {
451
// TODO: add handleEventTarget implementation
packages/react-native-renderer/src/ReactNativeHostConfig.js
+10
-2
@@ -206,11 +206,18 @@ export function getChildHostContext(
206
}
207
}
208
209
-export function getChildHostContextForEvent(
209
+export function getChildHostContextForEventComponent(
210
+ parentHostContext: HostContext,
211
+) {
212
+ // TODO: add getChildHostContextForEventComponent implementation
213
+ return parentHostContext;
214
+}
215
+
216
+export function getChildHostContextForEventTarget(
217
parentHostContext: HostContext,
218
type: Symbol | number,
219
) {
213
- // TODO: add getChildHostContextForEvent implementation
220
+ // TODO: add getChildHostContextForEventTarget implementation
221
return parentHostContext;
222
}
223
@@ -497,6 +504,7 @@ export function handleEventComponent(
504
export function handleEventTarget(
505
type: Symbol | number,
506
props: Props,
507
+ parentInstance: Container,
508
internalInstanceHandle: Object,
509
) {
510
// TODO: add handleEventTarget implementation
packages/react-noop-renderer/src/createReactNoop.js
+32
-16
@@ -24,13 +24,10 @@ import expect from 'expect';
24
import {
25
REACT_FRAGMENT_TYPE,
26
REACT_ELEMENT_TYPE,
27
- REACT_EVENT_COMPONENT_TYPE,
28
- REACT_EVENT_TARGET_TYPE,
27
REACT_EVENT_TARGET_TOUCH_HIT,
28
} from 'shared/ReactSymbols';
29
import warningWithoutStack from 'shared/warningWithoutStack';
30
import warning from 'shared/warning';
33
-import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
31
32
import {enableEventAPI} from 'shared/ReactFeatureFlags';
33
@@ -66,6 +63,7 @@ const NO_CONTEXT = {};
63
const UPPERCASE_CONTEXT = {};
64
const EVENT_COMPONENT_CONTEXT = {};
65
const EVENT_TARGET_CONTEXT = {};
66
+const EVENT_TOUCH_HIT_TARGET_CONTEXT = {};
67
const UPDATE_SIGNAL = {};
68
if (__DEV__) {
69
Object.freeze(NO_CONTEXT);
@@ -262,24 +260,32 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
260
return NO_CONTEXT;
261
},
262
265
- getChildHostContextForEvent(
263
+ getChildHostContextForEventComponent(parentHostContext: HostContext) {
264
+ if (__DEV__ && enableEventAPI) {
265
+ warning(
266
+ parentHostContext !== EVENT_TARGET_CONTEXT &&
267
+ parentHostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
268
+ 'validateDOMNesting: React event targets must not have event components as children.',
269
+ );
270
+ return EVENT_COMPONENT_CONTEXT;
271
+ }
272
+ return parentHostContext;
273
+ },
274
+
275
+ getChildHostContextForEventTarget(
276
parentHostContext: HostContext,
277
type: Symbol | number,
278
) {
279
if (__DEV__ && enableEventAPI) {
270
- if (type === REACT_EVENT_COMPONENT_TYPE) {
280
+ if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
281
warning(
272
- parentHostContext !== EVENT_TARGET_CONTEXT,
273
- 'validateDOMNesting: React event targets must not have event components as children.',
282
+ parentHostContext !== EVENT_COMPONENT_CONTEXT,
283
+ 'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
284
+ 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
285
);
275
- return EVENT_COMPONENT_CONTEXT;
276
- } else if (type === REACT_EVENT_TARGET_TYPE) {
277
- warning(
278
- parentHostContext === EVENT_COMPONENT_CONTEXT,
279
- 'validateDOMNesting: React event targets must be direct children of event components.',
280
- );
281
- return EVENT_TARGET_CONTEXT;
286
+ return EVENT_TOUCH_HIT_TARGET_CONTEXT;
287
}
288
+ return EVENT_TARGET_CONTEXT;
289
}
290
return parentHostContext;
291
},
@@ -294,6 +300,12 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
300
rootContainerInstance: Container,
301
hostContext: HostContext,
302
): Instance {
303
+ if (__DEV__ && enableEventAPI) {
304
+ warning(
305
+ hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
306
+ 'validateDOMNesting: <TouchHitTarget> must not have any children.',
307
+ );
308
+ }
309
if (type === 'errorInCompletePhase') {
310
throw new Error('Error in host config.');
311
}
@@ -368,6 +380,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
380
internalInstanceHandle: Object,
381
): TextInstance {
382
if (__DEV__ && enableEventAPI) {
383
+ warning(
384
+ hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
385
+ 'validateDOMNesting: <TouchHitTarget> must not have any children.',
386
+ );
387
warning(
388
hostContext !== EVENT_COMPONENT_CONTEXT,
389
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@@ -419,11 +435,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
435
handleEventTarget(
436
type: Symbol | number,
437
props: Props,
438
+ parentInstance: Container,
439
internalInstanceHandle: Object,
440
) {
441
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
425
- // Validates that there is a single element
426
- getElementFromTouchHitTarget(internalInstanceHandle);
442
+ // TODO
443
}
444
},
445
};
packages/react-reconciler/src/ReactFiberBeginWork.js
+9
-4
@@ -99,7 +99,8 @@ import type {SuspenseInstance} from './ReactFiberHostConfig';
99
import {
100
pushHostContext,
101
pushHostContainer,
102
- pushHostContextForEvent,
102
+ pushHostContextForEventComponent,
103
+ pushHostContextForEventTarget,
104
} from './ReactFiberHostContext';
105
import {
106
pushProvider,
@@ -1960,7 +1961,7 @@ function updateEventComponent(current, workInProgress, renderExpirationTime) {
1961
nextChildren,
1962
renderExpirationTime,
1963
);
1963
- pushHostContextForEvent(workInProgress);
1964
+ pushHostContextForEventComponent(workInProgress);
1965
return workInProgress.child;
1966
}
1967
@@ -1974,7 +1975,7 @@ function updateEventTarget(current, workInProgress, renderExpirationTime) {
1975
nextChildren,
1976
renderExpirationTime,
1977
);
1977
- pushHostContextForEvent(workInProgress);
1978
+ pushHostContextForEventTarget(workInProgress);
1979
return workInProgress.child;
1980
}
1981
@@ -2116,9 +2117,13 @@ function beginWork(
2117
break;
2118
}
2119
case EventComponent:
2120
+ if (enableEventAPI) {
2121
+ pushHostContextForEventComponent(workInProgress);
2122
+ }
2123
+ break;
2124
case EventTarget: {
2125
if (enableEventAPI) {
2121
- pushHostContextForEvent(workInProgress);
2126
+ pushHostContextForEventTarget(workInProgress);
2127
}
2128
break;
2129
}
packages/react-reconciler/src/ReactFiberCompleteWork.js
+13
-1
@@ -784,7 +784,19 @@ function completeWork(
784
if (enableEventAPI) {
785
popHostContext(workInProgress);
786
const type = workInProgress.type.type;
787
- handleEventTarget(type, newProps, workInProgress);
787
+ let node = workInProgress.return;
788
+ let parentHostInstance = null;
789
+ // Traverse up the fiber tree till we find a host component fiber
790
+ while (node !== null) {
791
+ if (node.tag === HostComponent) {
792
+ parentHostInstance = node.stateNode;
793
+ break;
794
+ }
795
+ node = node.return;
796
+ }
797
+ if (parentHostInstance !== null) {
798
+ handleEventTarget(type, newProps, parentHostInstance, workInProgress);
799
+ }
800
}
801
break;
802
}
packages/react-reconciler/src/ReactFiberHostContext.js
+25
-5
@@ -16,7 +16,8 @@ import invariant from 'shared/invariant';
16
import {
17
getChildHostContext,
18
getRootHostContext,
19
- getChildHostContextForEvent,
19
+ getChildHostContextForEventComponent,
20
+ getChildHostContextForEventTarget,
21
} from './ReactFiberHostConfig';
22
import {createCursor, push, pop} from './ReactFiberStack';
23
@@ -96,10 +97,28 @@ function pushHostContext(fiber: Fiber): void {
97
push(contextStackCursor, nextContext, fiber);
98
}
99
99
-function pushHostContextForEvent(fiber: Fiber): void {
100
+function pushHostContextForEventComponent(fiber: Fiber): void {
101
const context: HostContext = requiredContext(contextStackCursor.current);
101
- const eventTypeof = fiber.type.$$typeof;
102
- const nextContext = getChildHostContextForEvent(context, eventTypeof);
102
+ const nextContext = getChildHostContextForEventComponent(context);
103
+
104
+ // Don't push this Fiber's context unless it's unique.
105
+ if (context === nextContext) {
106
+ return;
107
+ }
108
+
109
+ // Track the context and the Fiber that provided it.
110
+ // This enables us to pop only Fibers that provide unique contexts.
111
+ push(contextFiberStackCursor, fiber, fiber);
112
+ push(contextStackCursor, nextContext, fiber);
113
+}
114
+
115
+function pushHostContextForEventTarget(fiber: Fiber): void {
116
+ const context: HostContext = requiredContext(contextStackCursor.current);
117
+ const eventTargetType = fiber.type.type;
118
+ const nextContext = getChildHostContextForEventTarget(
119
+ context,
120
+ eventTargetType,
121
+ );
122
123
// Don't push this Fiber's context unless it's unique.
124
if (context === nextContext) {
@@ -130,5 +149,6 @@ export {
149
popHostContext,
150
pushHostContainer,
151
pushHostContext,
133
- pushHostContextForEvent,
152
+ pushHostContextForEventComponent,
153
+ pushHostContextForEventTarget,
154
};
packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js
+51
-126
@@ -19,7 +19,7 @@ let ReactDOM;
19
let ReactDOMServer;
20
let ReactTestUtils;
21
let EventTarget;
22
-let ReactEvents;
22
+let ReactSymbols;
23
24
const noOpResponder = {
25
targetEventTypes: [],
@@ -28,19 +28,26 @@ const noOpResponder = {
28
29
function createReactEventComponent() {
30
return {
31
- $$typeof: Symbol.for('react.event_component'),
31
+ $$typeof: ReactSymbols.REACT_EVENT_COMPONENT_TYPE,
32
props: null,
33
responder: noOpResponder,
34
};
35
}
36
37
+function createReactEventTarget() {
38
+ return {
39
+ $$typeof: ReactSymbols.REACT_EVENT_TARGET_TYPE,
40
+ type: Symbol.for('react.event_target.test'),
41
+ };
42
+}
43
+
44
function init() {
45
jest.resetModules();
46
ReactFeatureFlags = require('shared/ReactFeatureFlags');
47
ReactFeatureFlags.enableEventAPI = true;
48
React = require('react');
49
Scheduler = require('scheduler');
43
- ReactEvents = require('react-events');
50
+ ReactSymbols = require('shared/ReactSymbols');
51
}
52
53
function initNoopRenderer() {
@@ -71,7 +78,7 @@ describe('ReactFiberEvents', () => {
78
beforeEach(() => {
79
initNoopRenderer();
80
EventComponent = createReactEventComponent();
74
- EventTarget = ReactEvents.TouchHitTarget;
81
+ EventTarget = createReactEventTarget();
82
});
83
84
it('should render a simple event component with a single child', () => {
@@ -139,11 +146,10 @@ describe('ReactFiberEvents', () => {
146
expect(() => {
147
ReactNoop.render(<Test />);
148
expect(Scheduler).toFlushWithoutYielding();
142
- }).toWarnDev([
149
+ }).toWarnDev(
150
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
151
'Wrap the child text "Hello world" in an element.',
145
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
146
- ]);
152
+ );
153
});
154
155
it('should warn when an event target has a direct text child #2', () => {
@@ -159,32 +165,13 @@ describe('ReactFiberEvents', () => {
165
expect(() => {
166
ReactNoop.render(<Test />);
167
expect(Scheduler).toFlushWithoutYielding();
162
- }).toWarnDev([
168
+ }).toWarnDev(
169
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
170
'Wrap the child text "Hello world" in an element.',
165
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
166
- ]);
167
- });
168
-
169
- it('should warn when an event target has more than one child', () => {
170
- const Test = () => (
171
- <EventComponent>
172
- <EventTarget>
173
- <span>Child 1</span>
174
- <span>Child 2</span>
175
- </EventTarget>
176
- </EventComponent>
177
- );
178
-
179
- expect(() => {
180
- ReactNoop.render(<Test />);
181
- expect(Scheduler).toFlushWithoutYielding();
182
- }).toWarnDev(
183
- 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
171
);
172
});
173
187
- it('should warn if an event target is not a direct child of an event component', () => {
174
+ it('should not warn if an event target is not a direct child of an event component', () => {
175
const Test = () => (
176
<EventComponent>
177
<div>
@@ -195,11 +182,12 @@ describe('ReactFiberEvents', () => {
182
</EventComponent>
183
);
184
198
- expect(() => {
199
- ReactNoop.render(<Test />);
200
- expect(Scheduler).toFlushWithoutYielding();
201
- }).toWarnDev(
202
- 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
185
+ ReactNoop.render(<Test />);
186
+ expect(Scheduler).toFlushWithoutYielding();
187
+ expect(ReactNoop).toMatchRenderedOutput(
188
+ <div>
189
+ <span>Child 1</span>
190
+ </div>,
191
);
192
});
193
@@ -319,7 +307,7 @@ describe('ReactFiberEvents', () => {
307
_updateCounter = updateCounter;
308
309
if (counter === 1) {
322
- return null;
310
+ return 'Text!';
311
}
312
313
return (
@@ -351,7 +339,8 @@ describe('ReactFiberEvents', () => {
339
});
340
expect(Scheduler).toFlushWithoutYielding();
341
}).toWarnDev(
354
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
342
+ 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
343
+ 'Wrap the child text "Text!" in an element.',
344
);
345
});
346
@@ -409,7 +398,7 @@ describe('ReactFiberEvents', () => {
398
beforeEach(() => {
399
initTestRenderer();
400
EventComponent = createReactEventComponent();
412
- EventTarget = ReactEvents.TouchHitTarget;
401
+ EventTarget = createReactEventTarget();
402
});
403
404
it('should render a simple event component with a single child', () => {
@@ -494,11 +483,10 @@ describe('ReactFiberEvents', () => {
483
expect(() => {
484
root.update(<Test />);
485
expect(Scheduler).toFlushWithoutYielding();
497
- }).toWarnDev([
486
+ }).toWarnDev(
487
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
488
'Wrap the child text "Hello world" in an element.',
500
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
501
- ]);
489
+ );
490
});
491
492
it('should warn when an event target has a direct text child #2', () => {
@@ -515,44 +503,13 @@ describe('ReactFiberEvents', () => {
503
expect(() => {
504
root.update(<Test />);
505
expect(Scheduler).toFlushWithoutYielding();
518
- }).toWarnDev([
506
+ }).toWarnDev(
507
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
508
'Wrap the child text "Hello world" in an element.',
521
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
522
- ]);
523
- });
524
-
525
- it('should warn when an event target has more than one child', () => {
526
- const Test = () => (
527
- <EventComponent>
528
- <EventTarget>
529
- <span>Child 1</span>
530
- <span>Child 2</span>
531
- </EventTarget>
532
- </EventComponent>
533
- );
534
-
535
- const root = ReactTestRenderer.create(null);
536
- expect(() => {
537
- root.update(<Test />);
538
- expect(Scheduler).toFlushWithoutYielding();
539
- }).toWarnDev(
540
- 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
541
- );
542
- // This should not fire a warning, as this is now valid.
543
- const Test2 = () => (
544
- <EventComponent>
545
- <EventTarget>
546
- <span>Child 1</span>
547
- </EventTarget>
548
- </EventComponent>
509
);
550
- root.update(<Test2 />);
551
- expect(Scheduler).toFlushWithoutYielding();
552
- expect(root).toMatchRenderedOutput(<span>Child 1</span>);
510
});
511
555
- it('should warn if an event target is not a direct child of an event component', () => {
512
+ it('should not warn if an event target is not a direct child of an event component', () => {
513
const Test = () => (
514
<EventComponent>
515
<div>
@@ -564,11 +521,12 @@ describe('ReactFiberEvents', () => {
521
);
522
523
const root = ReactTestRenderer.create(null);
567
- expect(() => {
568
- root.update(<Test />);
569
- expect(Scheduler).toFlushWithoutYielding();
570
- }).toWarnDev(
571
- 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
524
+ root.update(<Test />);
525
+ expect(Scheduler).toFlushWithoutYielding();
526
+ expect(root).toMatchRenderedOutput(
527
+ <div>
528
+ <span>Child 1</span>
529
+ </div>,
530
);
531
});
532
@@ -690,7 +648,7 @@ describe('ReactFiberEvents', () => {
648
_updateCounter = updateCounter;
649
650
if (counter === 1) {
693
- return null;
651
+ return 'Text!';
652
}
653
654
return (
@@ -722,7 +680,8 @@ describe('ReactFiberEvents', () => {
680
_updateCounter(counter => counter + 1);
681
});
682
}).toWarnDev(
725
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
683
+ 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
684
+ 'Wrap the child text "Text!" in an element.',
685
);
686
});
687
@@ -781,7 +740,7 @@ describe('ReactFiberEvents', () => {
740
beforeEach(() => {
741
initReactDOM();
742
EventComponent = createReactEventComponent();
784
- EventTarget = ReactEvents.TouchHitTarget;
743
+ EventTarget = createReactEventTarget();
744
});
745
746
it('should render a simple event component with a single child', () => {
@@ -865,11 +824,10 @@ describe('ReactFiberEvents', () => {
824
const container = document.createElement('div');
825
ReactDOM.render(<Test />, container);
826
expect(Scheduler).toFlushWithoutYielding();
868
- }).toWarnDev([
827
+ }).toWarnDev(
828
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
829
'Wrap the child text "Hello world" in an element.',
871
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
872
- ]);
830
+ );
831
});
832
833
it('should warn when an event target has a direct text child #2', () => {
@@ -886,44 +844,13 @@ describe('ReactFiberEvents', () => {
844
const container = document.createElement('div');
845
ReactDOM.render(<Test />, container);
846
expect(Scheduler).toFlushWithoutYielding();
889
- }).toWarnDev([
847
+ }).toWarnDev(
848
'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
849
'Wrap the child text "Hello world" in an element.',
892
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
893
- ]);
894
- });
895
-
896
- it('should warn when an event target has more than one child', () => {
897
- const Test = () => (
898
- <EventComponent>
899
- <EventTarget>
900
- <span>Child 1</span>
901
- <span>Child 2</span>
902
- </EventTarget>
903
- </EventComponent>
850
);
905
-
906
- const container = document.createElement('div');
907
- expect(() => {
908
- ReactDOM.render(<Test />, container);
909
- expect(Scheduler).toFlushWithoutYielding();
910
- }).toWarnDev(
911
- 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
912
- );
913
- // This should not fire a warning, as this is now valid.
914
- const Test2 = () => (
915
- <EventComponent>
916
- <EventTarget>
917
- <span>Child 1</span>
918
- </EventTarget>
919
- </EventComponent>
920
- );
921
- ReactDOM.render(<Test2 />, container);
922
- expect(Scheduler).toFlushWithoutYielding();
923
- expect(container.innerHTML).toBe('<span>Child 1</span>');
851
});
852
926
- it('should warn if an event target is not a direct child of an event component', () => {
853
+ it('should not warn if an event target is not a direct child of an event component', () => {
854
const Test = () => (
855
<EventComponent>
856
<div>
@@ -934,13 +861,10 @@ describe('ReactFiberEvents', () => {
861
</EventComponent>
862
);
863
937
- expect(() => {
938
- const container = document.createElement('div');
939
- ReactDOM.render(<Test />, container);
940
- expect(Scheduler).toFlushWithoutYielding();
941
- }).toWarnDev(
942
- 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
943
- );
864
+ const container = document.createElement('div');
865
+ ReactDOM.render(<Test />, container);
866
+ expect(Scheduler).toFlushWithoutYielding();
867
+ expect(container.innerHTML).toBe('<div><span>Child 1</span></div>');
868
});
869
870
it('should warn if an event target has an event component as a child', () => {
@@ -1052,7 +976,7 @@ describe('ReactFiberEvents', () => {
976
_updateCounter = updateCounter;
977
978
if (counter === 1) {
1055
- return null;
979
+ return 'Text!';
980
}
981
982
return (
@@ -1080,7 +1004,8 @@ describe('ReactFiberEvents', () => {
1004
});
1005
expect(Scheduler).toFlushWithoutYielding();
1006
}).toWarnDev(
1083
- 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
1007
+ 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
1008
+ 'Wrap the child text "Text!" in an element.',
1009
);
1010
});
1011
@@ -1134,7 +1059,7 @@ describe('ReactFiberEvents', () => {
1059
beforeEach(() => {
1060
initReactDOMServer();
1061
EventComponent = createReactEventComponent();
1137
- EventTarget = ReactEvents.TouchHitTarget;
1062
+ EventTarget = createReactEventTarget();
1063
});
1064
1065
it('should render a simple event component with a single child', () => {
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js
+4
-1
@@ -31,7 +31,10 @@ describe('ReactFiberHostContext', () => {
31
getChildHostContext: function() {
32
return null;
33
},
34
- getChildHostContextForEvent: function() {
34
+ getChildHostContextForEventComponent: function() {
35
+ return null;
36
+ },
37
+ getChildHostContextForEventTarget: function() {
38
return null;
39
},
40
shouldSetTextContent: function() {
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+4
-2
@@ -41,8 +41,10 @@ export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
41
export const getPublicInstance = $$$hostConfig.getPublicInstance;
42
export const getRootHostContext = $$$hostConfig.getRootHostContext;
43
export const getChildHostContext = $$$hostConfig.getChildHostContext;
44
-export const getChildHostContextForEvent =
45
- $$$hostConfig.getChildHostContextForEvent;
44
+export const getChildHostContextForEventComponent =
45
+ $$$hostConfig.getChildHostContextForEventComponent;
46
+export const getChildHostContextForEventTarget =
47
+ $$$hostConfig.getChildHostContextForEventTarget;
48
export const prepareForCommit = $$$hostConfig.prepareForCommit;
49
export const resetAfterCommit = $$$hostConfig.resetAfterCommit;
50
export const createInstance = $$$hostConfig.createInstance;
packages/react-test-renderer/src/ReactTestHostConfig.js
+35
-19
@@ -10,12 +10,7 @@
10
import warning from 'shared/warning';
11
12
import type {ReactEventResponder} from 'shared/ReactTypes';
13
-import {
14
- REACT_EVENT_COMPONENT_TYPE,
15
- REACT_EVENT_TARGET_TYPE,
16
- REACT_EVENT_TARGET_TOUCH_HIT,
17
-} from 'shared/ReactSymbols';
18
-import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
13
+import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
14
15
import {enableEventAPI} from 'shared/ReactFeatureFlags';
16
@@ -52,6 +47,7 @@ export * from 'shared/HostConfigWithNoHydration';
47
48
const EVENT_COMPONENT_CONTEXT = {};
49
const EVENT_TARGET_CONTEXT = {};
50
+const EVENT_TOUCH_HIT_TARGET_CONTEXT = {};
51
const NO_CONTEXT = {};
52
const UPDATE_SIGNAL = {};
53
if (__DEV__) {
@@ -127,24 +123,34 @@ export function getChildHostContext(
123
return NO_CONTEXT;
124
}
125
130
-export function getChildHostContextForEvent(
126
+export function getChildHostContextForEventComponent(
127
+ parentHostContext: HostContext,
128
+): HostContext {
129
+ if (__DEV__ && enableEventAPI) {
130
+ warning(
131
+ parentHostContext !== EVENT_TARGET_CONTEXT &&
132
+ parentHostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
133
+ 'validateDOMNesting: React event targets must not have event components as children.',
134
+ );
135
+ return EVENT_COMPONENT_CONTEXT;
136
+ }
137
+ return NO_CONTEXT;
138
+}
139
+
140
+export function getChildHostContextForEventTarget(
141
parentHostContext: HostContext,
142
type: Symbol | number,
143
): HostContext {
144
if (__DEV__ && enableEventAPI) {
135
- if (type === REACT_EVENT_COMPONENT_TYPE) {
145
+ if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
146
warning(
137
- parentHostContext !== EVENT_TARGET_CONTEXT,
138
- 'validateDOMNesting: React event targets must not have event components as children.',
147
+ parentHostContext !== EVENT_COMPONENT_CONTEXT,
148
+ 'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
149
+ 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
150
);
140
- return EVENT_COMPONENT_CONTEXT;
141
- } else if (type === REACT_EVENT_TARGET_TYPE) {
142
- warning(
143
- parentHostContext === EVENT_COMPONENT_CONTEXT,
144
- 'validateDOMNesting: React event targets must be direct children of event components.',
145
- );
146
- return EVENT_TARGET_CONTEXT;
151
+ return EVENT_TOUCH_HIT_TARGET_CONTEXT;
152
}
153
+ return EVENT_TARGET_CONTEXT;
154
}
155
return NO_CONTEXT;
156
}
@@ -164,6 +170,12 @@ export function createInstance(
170
hostContext: Object,
171
internalInstanceHandle: Object,
172
): Instance {
173
+ if (__DEV__ && enableEventAPI) {
174
+ warning(
175
+ hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
176
+ 'validateDOMNesting: <TouchHitTarget> must not have any children.',
177
+ );
178
+ }
179
return {
180
type,
181
props,
@@ -221,6 +233,10 @@ export function createTextInstance(
233
internalInstanceHandle: Object,
234
): TextInstance {
235
if (__DEV__ && enableEventAPI) {
236
+ warning(
237
+ hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
238
+ 'validateDOMNesting: <TouchHitTarget> must not have any children.',
239
+ );
240
warning(
241
hostContext !== EVENT_COMPONENT_CONTEXT,
242
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@@ -320,10 +336,10 @@ export function handleEventComponent(
336
export function handleEventTarget(
337
type: Symbol | number,
338
props: Props,
339
+ parentInstance: Container,
340
internalInstanceHandle: Object,
341
) {
342
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
326
- // Validates that there is a single element
327
- getElementFromTouchHitTarget(internalInstanceHandle);
343
+ // TODO
344
}
345
}
packages/shared/getElementFromTouchHitTarget.js
deleted
-85
@@ -1,85 +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
- * @flow
8
- */
9
-
10
-import type {Fiber} from 'react-reconciler/src/ReactFiber';
11
-
12
-import {HostComponent} from 'shared/ReactWorkTags';
13
-import warning from 'shared/warning';
14
-
15
-type HostContext = Object;
16
-
17
-type TextInstance =
18
- | Text
19
- | {|
20
- text: string,
21
- id: number,
22
- hidden: boolean,
23
- context: HostContext,
24
- |};
25
-
26
-type Instance =
27
- | Element
28
- | {|
29
- type: string,
30
- id: number,
31
- children: Array<Instance | TextInstance>,
32
- text: string | null,
33
- prop: any,
34
- hidden: boolean,
35
- context: HostContext,
36
- |};
37
-
38
-export default function getElementFromTouchHitTarget(
39
- targetFiber: Fiber,
40
-): null | Instance {
41
- // Traverse through child fibers and find the first host components
42
- let node = targetFiber.child;
43
- let hostComponent = null;
44
-
45
- while (node !== null) {
46
- if (node.tag === HostComponent) {
47
- if (__DEV__) {
48
- if (hostComponent === null) {
49
- hostComponent = node.stateNode;
50
- } else {
51
- warning(
52
- false,
53
- '<TouchHitTarget> must only have a single DOM element as a child. ' +
54
- 'Found many children.',
55
- );
56
- }
57
- while (node !== null) {
58
- if (node === targetFiber) {
59
- return hostComponent;
60
- } else if (node.sibling !== null) {
61
- node = node.sibling;
62
- break;
63
- }
64
- node = node.return;
65
- }
66
- } else {
67
- return node.stateNode;
68
- }
69
- } else if (node.child !== null) {
70
- node = node.child;
71
- } else if (node.sibling !== null) {
72
- node = node.sibling;
73
- } else {
74
- break;
75
- }
76
- }
77
- if (__DEV__) {
78
- warning(
79
- hostComponent !== null,
80
- '<TouchHitTarget> must have a single DOM element as a child. ' +
81
- 'Found no children.',
82
- );
83
- }
84
- return hostComponent;
85
-}