Skip to content

Commit 017d6f1

Browse files
authored
Experimental Event API: add rootEventTypes support to event responders (#15475)
* Adds rootEventTypes
1 parent 784ebd8 commit 017d6f1

6 files changed

Lines changed: 141 additions & 8 deletions

File tree

‎packages/react-dom/src/client/ReactDOMComponent.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,7 @@ export function listenToEventResponderEventTypes(
13021302
if(__DEV__){
13031303
warning(
13041304
typeoftargetEventType==='object'&&targetEventType!==null,
1305-
'Event Responder: invalid entry in targetEventTypes array. '+
1305+
'Event Responder: invalid entry in event types array. '+
13061306
'Entry must be string or an object. Instead, got %s.',
13071307
targetEventType,
13081308
);

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
setEnabledasReactBrowserEventEmitterSetEnabled,
3535
}from'../events/ReactBrowserEventEmitter';
3636
import{Namespaces,getChildNamespace}from'../shared/DOMNamespaces';
37+
import{addRootEventTypesForComponentInstance}from'../events/DOMEventResponderSystem';
3738
import{
3839
ELEMENT_NODE,
3940
TEXT_NODE,
@@ -906,10 +907,18 @@ export function updateEventComponent(
906907
if(enableEventAPI){
907908
constrootContainerInstance=((eventComponentInstance.rootInstance: any): Container);
908909
constrootElement=rootContainerInstance.ownerDocument;
909-
listenToEventResponderEventTypes(
910-
eventComponentInstance.responder.targetEventTypes,
911-
rootElement,
912-
);
910+
constresponder=eventComponentInstance.responder;
911+
const{rootEventTypes,targetEventTypes}=responder;
912+
if(targetEventTypes!==undefined){
913+
listenToEventResponderEventTypes(targetEventTypes,rootElement);
914+
}
915+
if(rootEventTypes!==undefined){
916+
addRootEventTypesForComponentInstance(
917+
eventComponentInstance,
918+
rootEventTypes,
919+
);
920+
listenToEventResponderEventTypes(rootEventTypes,rootElement);
921+
}
913922
}
914923
}
915924

‎packages/react-dom/src/events/DOMEventResponderSystem.js‎

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,20 @@ const eventResponderContext: ReactResponderContext = {
206206
rootEventComponentInstances,
207207
);
208208
}
209-
rootEventComponentInstances.add(
210-
((currentInstance: any): ReactEventComponentInstance),
209+
constcomponentInstance=((currentInstance: any): ReactEventComponentInstance);
210+
letrootEventTypesSet=componentInstance.rootEventTypes;
211+
if(rootEventTypesSet===null){
212+
rootEventTypesSet=componentInstance.rootEventTypes=newSet();
213+
}
214+
invariant(
215+
!rootEventTypesSet.has(topLevelEventType),
216+
'addRootEventTypes() found a duplicate root event '+
217+
'type of "%s". This might be because the event type exists in the event responder "rootEventTypes" '+
218+
'array or because of a previous addRootEventTypes() using this root event type.',
219+
rootEventType,
211220
);
221+
rootEventTypesSet.add(topLevelEventType);
222+
rootEventComponentInstances.add(componentInstance);
212223
}
213224
},
214225
removeRootEventTypes(
@@ -222,6 +233,11 @@ const eventResponderContext: ReactResponderContext = {
222233
letrootEventComponents=rootEventTypesToEventComponentInstances.get(
223234
topLevelEventType,
224235
);
236+
letrootEventTypesSet=((currentInstance: any): ReactEventComponentInstance)
237+
.rootEventTypes;
238+
if(rootEventTypesSet!==null){
239+
rootEventTypesSet.delete(topLevelEventType);
240+
}
225241
if(rootEventComponents!==undefined){
226242
rootEventComponents.delete(
227243
((currentInstance: any): ReactEventComponentInstance),
@@ -636,6 +652,20 @@ export function unmountEventResponder(
636652
if(responder.onOwnershipChange!==undefined){
637653
ownershipChangeListeners.delete(eventComponentInstance);
638654
}
655+
constrootEventTypesSet=eventComponentInstance.rootEventTypes;
656+
if(rootEventTypesSet!==null){
657+
constrootEventTypes=Array.from(rootEventTypesSet);
658+
659+
for(leti=0;i<rootEventTypes.length;i++){
660+
consttopLevelEventType=rootEventTypes[i];
661+
letrootEventComponentInstances=rootEventTypesToEventComponentInstances.get(
662+
topLevelEventType,
663+
);
664+
if(rootEventComponentInstances!==undefined){
665+
rootEventComponentInstances.delete(eventComponentInstance);
666+
}
667+
}
668+
}
639669
}
640670

641671
functionvalidateResponderContext(): void{
@@ -671,3 +701,32 @@ export function dispatchEventForResponderEventSystem(
671701
}
672702
}
673703
}
704+
705+
exportfunctionaddRootEventTypesForComponentInstance(
706+
eventComponentInstance: ReactEventComponentInstance,
707+
rootEventTypes: Array<ReactEventResponderEventType>,
708+
): void{
709+
for(leti=0;i<rootEventTypes.length;i++){
710+
constrootEventType=rootEventTypes[i];
711+
consttopLevelEventType=
712+
typeofrootEventType==='string' ? rootEventType : rootEventType.name;
713+
letrootEventComponentInstances=rootEventTypesToEventComponentInstances.get(
714+
topLevelEventType,
715+
);
716+
if(rootEventComponentInstances===undefined){
717+
rootEventComponentInstances=newSet();
718+
rootEventTypesToEventComponentInstances.set(
719+
topLevelEventType,
720+
rootEventComponentInstances,
721+
);
722+
}
723+
letrootEventTypesSet=eventComponentInstance.rootEventTypes;
724+
if(rootEventTypesSet===null){
725+
rootEventTypesSet=eventComponentInstance.rootEventTypes=newSet();
726+
}
727+
rootEventTypesSet.add(topLevelEventType);
728+
rootEventComponentInstances.add(
729+
((eventComponentInstance: any): ReactEventComponentInstance),
730+
);
731+
}
732+
}

‎packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ let ReactSymbols;
1616

1717
functioncreateReactEventComponent(
1818
targetEventTypes,
19+
rootEventTypes,
1920
createInitialState,
2021
onEvent,
2122
onEventCapture,
@@ -26,6 +27,7 @@ function createReactEventComponent(
2627
){
2728
consttestEventResponder={
2829
targetEventTypes,
30+
rootEventTypes,
2931
createInitialState,
3032
onEvent,
3133
onEventCapture,
@@ -90,6 +92,7 @@ describe('DOMEventResponderSystem', () => {
9092
constClickEventComponent=createReactEventComponent(
9193
['click'],
9294
undefined,
95+
undefined,
9396
(event,context,props)=>{
9497
eventResponderFiredCount++;
9598
eventLog.push({
@@ -163,6 +166,7 @@ describe('DOMEventResponderSystem', () => {
163166
constClickEventComponent=createReactEventComponent(
164167
['click'],
165168
undefined,
169+
undefined,
166170
(event,context,props)=>{
167171
eventLog.push({
168172
name: event.type,
@@ -217,6 +221,7 @@ describe('DOMEventResponderSystem', () => {
217221
constClickEventComponent=createReactEventComponent(
218222
['click'],
219223
undefined,
224+
undefined,
220225
(event,context,props)=>{
221226
eventResponderFiredCount++;
222227
eventLog.push({
@@ -288,6 +293,7 @@ describe('DOMEventResponderSystem', () => {
288293
constClickEventComponentA=createReactEventComponent(
289294
['click'],
290295
undefined,
296+
undefined,
291297
(event,context,props)=>{
292298
eventLog.push(`A [bubble]`);
293299
},
@@ -299,6 +305,7 @@ describe('DOMEventResponderSystem', () => {
299305
constClickEventComponentB=createReactEventComponent(
300306
['click'],
301307
undefined,
308+
undefined,
302309
(event,context,props)=>{
303310
eventLog.push(`B [bubble]`);
304311
},
@@ -336,6 +343,7 @@ describe('DOMEventResponderSystem', () => {
336343
constClickEventComponent=createReactEventComponent(
337344
['click'],
338345
undefined,
346+
undefined,
339347
(event,context,props)=>{
340348
eventLog.push(`${props.name} [bubble]`);
341349
},
@@ -377,6 +385,7 @@ describe('DOMEventResponderSystem', () => {
377385
constClickEventComponent=createReactEventComponent(
378386
['click'],
379387
undefined,
388+
undefined,
380389
(event,context,props)=>{
381390
eventLog.push(`${props.name} [bubble]`);
382391
},
@@ -413,6 +422,7 @@ describe('DOMEventResponderSystem', () => {
413422
constClickEventComponent=createReactEventComponent(
414423
['click'],
415424
undefined,
425+
undefined,
416426
(event,context,props)=>{
417427
if(props.onMagicClick){
418428
constsyntheticEvent={
@@ -505,6 +515,7 @@ describe('DOMEventResponderSystem', () => {
505515
constLongPressEventComponent=createReactEventComponent(
506516
['click'],
507517
undefined,
518+
undefined,
508519
(event,context,props)=>{
509520
handleEvent(event,context,props,'bubble');
510521
},
@@ -551,6 +562,7 @@ describe('DOMEventResponderSystem', () => {
551562
undefined,
552563
undefined,
553564
undefined,
565+
undefined,
554566
(event,context,props,state)=>{},
555567
()=>{
556568
onUnmountFired++;
@@ -573,6 +585,7 @@ describe('DOMEventResponderSystem', () => {
573585

574586
constEventComponent=createReactEventComponent(
575587
[],
588+
undefined,
576589
()=>({
577590
incrementAmount: 5,
578591
}),
@@ -603,6 +616,7 @@ describe('DOMEventResponderSystem', () => {
603616
constEventComponent=createReactEventComponent(
604617
['click'],
605618
undefined,
619+
undefined,
606620
(event,context,props,state)=>{
607621
ownershipGained=context.requestOwnership();
608622
},
@@ -641,6 +655,7 @@ describe('DOMEventResponderSystem', () => {
641655
constEventComponent=createReactEventComponent(
642656
['click'],
643657
undefined,
658+
undefined,
644659
(event,context,props,state)=>{
645660
queryResult=Array.from(
646661
context.getEventTargetsFromTarget(event.target),
@@ -696,6 +711,7 @@ describe('DOMEventResponderSystem', () => {
696711
constEventComponent=createReactEventComponent(
697712
['click'],
698713
undefined,
714+
undefined,
699715
(event,context,props,state)=>{
700716
queryResult=context.getEventTargetsFromTarget(
701717
event.target,
@@ -743,6 +759,7 @@ describe('DOMEventResponderSystem', () => {
743759
constEventComponent=createReactEventComponent(
744760
['click'],
745761
undefined,
762+
undefined,
746763
(event,context,props,state)=>{
747764
queryResult=context.getEventTargetsFromTarget(
748765
event.target,
@@ -795,6 +812,7 @@ describe('DOMEventResponderSystem', () => {
795812
constEventComponent=createReactEventComponent(
796813
['click'],
797814
undefined,
815+
undefined,
798816
(event,context,props,state)=>{
799817
queryResult=context.getEventTargetsFromTarget(
800818
event.target,
@@ -855,4 +873,48 @@ describe('DOMEventResponderSystem', () => {
855873
]);
856874
expect(queryResult3).toEqual([]);
857875
});
876+
877+
it('the event responder root listeners should fire on a root click event',()=>{
878+
leteventResponderFiredCount=0;
879+
leteventLog=[];
880+
881+
constClickEventComponent=createReactEventComponent(
882+
undefined,
883+
['click'],
884+
undefined,
885+
undefined,
886+
undefined,
887+
event=>{
888+
eventResponderFiredCount++;
889+
eventLog.push({
890+
name: event.type,
891+
passive: event.passive,
892+
passiveSupported: event.passiveSupported,
893+
phase: 'root',
894+
});
895+
},
896+
);
897+
898+
constTest=()=>(
899+
<ClickEventComponent>
900+
<button>Click me!</button>
901+
</ClickEventComponent>
902+
);
903+
904+
ReactDOM.render(<Test/>,container);
905+
expect(container.innerHTML).toBe('<button>Click me!</button>');
906+
907+
// Clicking the button should trigger the event responder onEvent() twice
908+
dispatchClickEvent(document.body);
909+
expect(eventResponderFiredCount).toBe(1);
910+
expect(eventLog.length).toBe(1);
911+
expect(eventLog).toEqual([
912+
{
913+
name: 'click',
914+
passive: false,
915+
passiveSupported: false,
916+
phase: 'root',
917+
},
918+
]);
919+
});
858920
});

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ function completeWork(
816816
context: null,
817817
props: newProps,
818818
responder,
819+
rootEventTypes: null,
819820
rootInstance: rootContainerInstance,
820821
state: responderState,
821822
};

‎packages/shared/ReactTypes.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export type ReactEventResponderEventType =
8686
|{name: string,passive?: boolean,capture?: boolean};
8787

8888
exporttypeReactEventResponder={
89-
targetEventTypes: Array<ReactEventResponderEventType>,
89+
targetEventTypes?: Array<ReactEventResponderEventType>,
90+
rootEventTypes?: Array<ReactEventResponderEventType>,
9091
createInitialState?: (props: null|Object)=>Object,
9192
stopLocalPropagation: boolean,
9293
onEvent?: (
@@ -123,6 +124,7 @@ export type ReactEventComponentInstance = {|
123124
context: null|Object,
124125
props: null|Object,
125126
responder: ReactEventResponder,
127+
rootEventTypes: null|Set<string>,
126128
rootInstance: mixed,
127129
state: null|Object,
128130
|};

0 commit comments

Comments
 (0)