Skip to content

Commit b83e01c

Browse files
authored
Adds more scaffolding for experimental event API (#15112)
* Adds more scaffolding for experimental event API
1 parent daeda44 commit b83e01c

19 files changed

+1066
-279
lines changed

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ export function getChildHostContext() {
340340
return NO_CONTEXT;
341341
}
342342

343+
export function getChildHostContextForEvent() {
344+
return NO_CONTEXT;
345+
}
346+
343347
export const scheduleTimeout = setTimeout;
344348
export const cancelTimeout = clearTimeout;
345349
export const noTimeout = -1;
@@ -440,7 +444,7 @@ export function handleEventComponent(
440444
}
441445

442446
export function handleEventTarget(
443-
type: string,
447+
type: Symbol | number,
444448
props: Props,
445449
internalInstanceHandle: Object,
446450
) {

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
4444

4545
import type {DOMContainer} from './ReactDOM';
4646
import type {ReactEventResponder} from 'shared/ReactTypes';
47+
import {
48+
REACT_EVENT_COMPONENT_TYPE,
49+
REACT_EVENT_TARGET_TYPE,
50+
REACT_EVENT_TARGET_TOUCH_HIT,
51+
} from 'shared/ReactSymbols';
52+
import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
4753

4854
export type Type = string;
4955
export type Props = {
@@ -65,6 +71,10 @@ export type PublicInstance = Element | Text;
6571
type HostContextDev = {
6672
namespace: string,
6773
ancestorInfo: mixed,
74+
eventData: null | {|
75+
isEventComponent?: boolean,
76+
isEventTarget?: boolean,
77+
|},
6878
};
6979
type HostContextProd = string;
7080
export type HostContext = HostContextDev | HostContextProd;
@@ -73,7 +83,11 @@ export type ChildSet = void; // Unused
7383
export type TimeoutHandle = TimeoutID;
7484
export type NoTimeout = -1;
7585

76-
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
86+
import {
87+
enableSuspenseServerRenderer,
88+
enableEventAPI,
89+
} from 'shared/ReactFeatureFlags';
90+
import warning from 'shared/warning';
7791

7892
// Intentionally not named imports because Rollup would
7993
// use dynamic dispatch for CommonJS interop named imports.
@@ -142,7 +156,7 @@ export function getRootHostContext(
142156
if (__DEV__) {
143157
const validatedTag = type.toLowerCase();
144158
const ancestorInfo = updatedAncestorInfo(null, validatedTag);
145-
return {namespace, ancestorInfo};
159+
return {namespace, ancestorInfo, eventData: null};
146160
}
147161
return namespace;
148162
}
@@ -159,12 +173,42 @@ export function getChildHostContext(
159173
parentHostContextDev.ancestorInfo,
160174
type,
161175
);
162-
return {namespace, ancestorInfo};
176+
return {namespace, ancestorInfo, eventData: null};
163177
}
164178
const parentNamespace = ((parentHostContext: any): HostContextProd);
165179
return getChildNamespace(parentNamespace, type);
166180
}
167181

182+
export function getChildHostContextForEvent(
183+
parentHostContext: HostContext,
184+
type: Symbol | number,
185+
): HostContext {
186+
if (__DEV__) {
187+
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
188+
const {namespace, ancestorInfo} = parentHostContextDev;
189+
let eventData = null;
190+
191+
if (type === REACT_EVENT_COMPONENT_TYPE) {
192+
eventData = {
193+
isEventComponent: true,
194+
isEventTarget: false,
195+
};
196+
} else if (type === REACT_EVENT_TARGET_TYPE) {
197+
warning(
198+
parentHostContextDev.eventData !== null &&
199+
parentHostContextDev.eventData.isEventComponent,
200+
'validateDOMNesting: React event targets must be direct children of event components.',
201+
);
202+
eventData = {
203+
isEventComponent: false,
204+
isEventTarget: true,
205+
};
206+
}
207+
return {namespace, ancestorInfo, eventData};
208+
}
209+
return parentHostContext;
210+
}
211+
168212
export function getPublicInstance(instance: Instance): * {
169213
return instance;
170214
}
@@ -296,6 +340,23 @@ export function createTextInstance(
296340
if (__DEV__) {
297341
const hostContextDev = ((hostContext: any): HostContextDev);
298342
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
343+
if (enableEventAPI) {
344+
const eventData = hostContextDev.eventData;
345+
if (eventData !== null) {
346+
warning(
347+
!eventData.isEventComponent,
348+
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
349+
'Wrap the child text "%s" in an element.',
350+
text,
351+
);
352+
warning(
353+
!eventData.isEventTarget,
354+
'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
355+
'Wrap the child text "%s" in an element.',
356+
text,
357+
);
358+
}
359+
}
299360
}
300361
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
301362
precacheFiberNode(internalInstanceHandle, textNode);
@@ -804,9 +865,25 @@ export function handleEventComponent(
804865
}
805866

806867
export function handleEventTarget(
807-
type: string,
868+
type: Symbol | number,
808869
props: Props,
809870
internalInstanceHandle: Object,
810871
) {
811-
// TODO: add handleEventTarget implementation
872+
// Touch target hit slop handling
873+
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
874+
// Validates that there is a single element
875+
const element = getElementFromTouchHitTarget(internalInstanceHandle);
876+
if (element !== null) {
877+
// We update the event target state node to be that of the element.
878+
// We can then diff this entry to determine if we need to add the
879+
// hit slop element, or change the dimensions of the hit slop.
880+
const lastElement = internalInstanceHandle.stateNode;
881+
if (lastElement !== element) {
882+
internalInstanceHandle.stateNode = element;
883+
// TODO: Create the hit slop element and attach it to the element
884+
} else {
885+
// TODO: Diff the left, top, right, bottom props
886+
}
887+
}
888+
}
812889
}

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,14 @@ export function getChildHostContext(
283283
}
284284
}
285285

286+
export function getChildHostContextForEvent(
287+
parentHostContext: HostContext,
288+
type: Symbol | number,
289+
) {
290+
// TODO: add getChildHostContextForEvent implementation
291+
return parentHostContext;
292+
}
293+
286294
export function getPublicInstance(instance: Instance): * {
287295
return instance.canonical;
288296
}
@@ -428,7 +436,7 @@ export function handleEventComponent(
428436
}
429437

430438
export function handleEventTarget(
431-
type: string,
439+
type: Symbol | number,
432440
props: Props,
433441
internalInstanceHandle: Object,
434442
) {

packages/react-native-renderer/src/ReactNativeHostConfig.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ export function getChildHostContext(
206206
}
207207
}
208208

209+
export function getChildHostContextForEvent(
210+
parentHostContext: HostContext,
211+
type: Symbol | number,
212+
) {
213+
// TODO: add getChildHostContextForEvent implementation
214+
return parentHostContext;
215+
}
216+
209217
export function getPublicInstance(instance: Instance): * {
210218
return instance;
211219
}
@@ -487,7 +495,7 @@ export function handleEventComponent(
487495
}
488496

489497
export function handleEventTarget(
490-
type: string,
498+
type: Symbol | number,
491499
props: Props,
492500
internalInstanceHandle: Object,
493501
) {

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,18 @@ import type {ReactNodeList} from 'shared/ReactTypes';
2121
import * as Scheduler from 'scheduler/unstable_mock';
2222
import {createPortal} from 'shared/ReactPortal';
2323
import expect from 'expect';
24-
import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
24+
import {
25+
REACT_FRAGMENT_TYPE,
26+
REACT_ELEMENT_TYPE,
27+
REACT_EVENT_COMPONENT_TYPE,
28+
REACT_EVENT_TARGET_TYPE,
29+
REACT_EVENT_TARGET_TOUCH_HIT,
30+
} from 'shared/ReactSymbols';
2531
import warningWithoutStack from 'shared/warningWithoutStack';
32+
import warning from 'shared/warning';
33+
import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
34+
35+
import {enableEventAPI} from 'shared/ReactFeatureFlags';
2636

2737
// for .act's return value
2838
type Thenable = {
@@ -54,6 +64,8 @@ type HostContext = Object;
5464

5565
const NO_CONTEXT = {};
5666
const UPPERCASE_CONTEXT = {};
67+
const EVENT_COMPONENT_CONTEXT = {};
68+
const EVENT_TARGET_CONTEXT = {};
5769
const UPDATE_SIGNAL = {};
5870
if (__DEV__) {
5971
Object.freeze(NO_CONTEXT);
@@ -250,6 +262,24 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
250262
return NO_CONTEXT;
251263
},
252264

265+
getChildHostContextForEvent(
266+
parentHostContext: HostContext,
267+
type: Symbol | number,
268+
) {
269+
if (__DEV__ && enableEventAPI) {
270+
if (type === REACT_EVENT_COMPONENT_TYPE) {
271+
return EVENT_COMPONENT_CONTEXT;
272+
} else if (type === REACT_EVENT_TARGET_TYPE) {
273+
warning(
274+
parentHostContext === EVENT_COMPONENT_CONTEXT,
275+
'validateDOMNesting: React event targets must be direct children of event components.',
276+
);
277+
return EVENT_TARGET_CONTEXT;
278+
}
279+
}
280+
return parentHostContext;
281+
},
282+
253283
getPublicInstance(instance) {
254284
return instance;
255285
},
@@ -333,6 +363,20 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
333363
hostContext: Object,
334364
internalInstanceHandle: Object,
335365
): TextInstance {
366+
if (__DEV__ && enableEventAPI) {
367+
warning(
368+
hostContext !== EVENT_COMPONENT_CONTEXT,
369+
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
370+
'Wrap the child text "%s" in an element.',
371+
text,
372+
);
373+
warning(
374+
hostContext !== EVENT_TARGET_CONTEXT,
375+
'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
376+
'Wrap the child text "%s" in an element.',
377+
text,
378+
);
379+
}
336380
if (hostContext === UPPERCASE_CONTEXT) {
337381
text = text.toUpperCase();
338382
}
@@ -363,6 +407,21 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
363407

364408
isPrimaryRenderer: true,
365409
supportsHydration: false,
410+
411+
handleEventComponent() {
412+
// NO-OP
413+
},
414+
415+
handleEventTarget(
416+
type: Symbol | number,
417+
props: Props,
418+
internalInstanceHandle: Object,
419+
) {
420+
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
421+
// Validates that there is a single element
422+
getElementFromTouchHitTarget(internalInstanceHandle);
423+
}
424+
},
366425
};
367426

368427
const hostConfig = useMutation

0 commit comments

Comments
 (0)