Skip to content

Commit e85940c

Browse files
author
Luna Wei
committed
Add skipBubbling property to dispatch config
1 parent 5d08a24 commit e85940c

File tree

5 files changed

+125
-4
lines changed

5 files changed

+125
-4
lines changed

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ function getParent(inst) {
6666
/**
6767
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
6868
*/
69-
export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
69+
export function traverseTwoPhase(
70+
inst: Object,
71+
fn: Function,
72+
arg: Function,
73+
skipBubbling: boolean,
74+
) {
7075
const path = [];
7176
while (inst) {
7277
path.push(inst);
@@ -76,14 +81,26 @@ export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
7681
for (i = path.length; i-- > 0; ) {
7782
fn(path[i], 'captured', arg);
7883
}
79-
for (i = 0; i < path.length; i++) {
80-
fn(path[i], 'bubbled', arg);
84+
if (skipBubbling) {
85+
// Dispatch on target only
86+
fn(path[0], 'bubbled', arg);
87+
} else {
88+
for (i = 0; i < path.length; i++) {
89+
fn(path[i], 'bubbled', arg);
90+
}
8191
}
8292
}
8393

8494
function accumulateTwoPhaseDispatchesSingle(event) {
8595
if (event && event.dispatchConfig.phasedRegistrationNames) {
86-
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
96+
const skipBubbling = !!event.dispatchConfig.phasedRegistrationNames
97+
.skipBubbling;
98+
traverseTwoPhase(
99+
event._targetInst,
100+
accumulateDirectionalDispatches,
101+
event,
102+
skipBubbling,
103+
);
87104
}
88105
}
89106

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export type ViewConfig = $ReadOnly<{
7373
phasedRegistrationNames: $ReadOnly<{
7474
captured: string,
7575
bubbled: string,
76+
skipBubble?: ?boolean,
7677
}>,
7778
}>,
7879
...,

packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,106 @@ describe('ReactFabric', () => {
633633
expect(touchStart2).toBeCalled();
634634
});
635635

636+
describe('skipBubbling', () => {
637+
it.only('should skip bubbling to ancestor if specified', () => {
638+
const View = createReactNativeComponentClass('RCTView', () => ({
639+
validAttributes: {},
640+
uiViewClassName: 'RCTView',
641+
bubblingEventTypes: {
642+
topDefaultBubblingEvent: {
643+
phasedRegistrationNames: {
644+
captured: 'onDefaultBubblingEventCapture',
645+
bubbled: 'onDefaultBubblingEvent',
646+
},
647+
},
648+
topBubblingEvent: {
649+
phasedRegistrationNames: {
650+
captured: 'onBubblingEventCapture',
651+
bubbled: 'onBubblingEvent',
652+
skipBubbling: false,
653+
},
654+
},
655+
topSkipBubblingEvent: {
656+
phasedRegistrationNames: {
657+
captured: 'onSkippedBubblingEventCapture',
658+
bubbled: 'onSkippedBubblingEvent',
659+
skipBubbling: true,
660+
},
661+
},
662+
},
663+
}));
664+
const ancestorBubble = jest.fn();
665+
const ancestorCapture = jest.fn();
666+
const targetBubble = jest.fn();
667+
const targetCapture = jest.fn();
668+
669+
const event = {};
670+
671+
act(() => {
672+
ReactFabric.render(
673+
<View
674+
onSkippedBubblingEventCapture={ancestorCapture}
675+
onDefaultBubblingEventCapture={ancestorCapture}
676+
onBubblingEventCapture={ancestorCapture}
677+
onSkippedBubblingEvent={ancestorBubble}
678+
onDefaultBubblingEvent={ancestorBubble}
679+
onBubblingEvent={ancestorBubble}>
680+
<View
681+
onSkippedBubblingEventCapture={targetCapture}
682+
onDefaultBubblingEventCapture={targetCapture}
683+
onBubblingEventCapture={targetCapture}
684+
onSkippedBubblingEvent={targetBubble}
685+
onDefaultBubblingEvent={targetBubble}
686+
onBubblingEvent={targetBubble}
687+
/>
688+
</View>,
689+
11,
690+
);
691+
});
692+
693+
expect(nativeFabricUIManager.createNode.mock.calls.length).toBe(2);
694+
expect(nativeFabricUIManager.registerEventHandler.mock.calls.length).toBe(
695+
1,
696+
);
697+
const [
698+
,
699+
,
700+
,
701+
,
702+
childInstance,
703+
] = nativeFabricUIManager.createNode.mock.calls[0];
704+
const [
705+
dispatchEvent,
706+
] = nativeFabricUIManager.registerEventHandler.mock.calls[0];
707+
708+
dispatchEvent(childInstance, 'topDefaultBubblingEvent', event);
709+
expect(targetBubble).toHaveBeenCalledTimes(1);
710+
expect(targetCapture).toHaveBeenCalledTimes(1);
711+
expect(ancestorCapture).toHaveBeenCalledTimes(1);
712+
expect(ancestorBubble).toHaveBeenCalledTimes(1);
713+
ancestorBubble.mockReset();
714+
ancestorCapture.mockReset();
715+
targetBubble.mockReset();
716+
targetCapture.mockReset();
717+
718+
dispatchEvent(childInstance, 'topBubblingEvent', event);
719+
expect(targetBubble).toHaveBeenCalledTimes(1);
720+
expect(targetCapture).toHaveBeenCalledTimes(1);
721+
expect(ancestorCapture).toHaveBeenCalledTimes(1);
722+
expect(ancestorBubble).toHaveBeenCalledTimes(1);
723+
ancestorBubble.mockReset();
724+
ancestorCapture.mockReset();
725+
targetBubble.mockReset();
726+
targetCapture.mockReset();
727+
728+
dispatchEvent(childInstance, 'topSkipBubblingEvent', event);
729+
expect(targetBubble).toHaveBeenCalledTimes(1);
730+
expect(targetCapture).toHaveBeenCalledTimes(1);
731+
expect(ancestorCapture).toHaveBeenCalledTimes(1);
732+
expect(ancestorBubble).not.toBeCalled();
733+
});
734+
});
735+
636736
it('dispatches event with target as instance', () => {
637737
const View = createReactNativeComponentClass('RCTView', () => ({
638738
validAttributes: {

packages/react-native-renderer/src/legacy-events/ReactSyntheticEventType.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type DispatchConfig = {|
1616
phasedRegistrationNames: {|
1717
bubbled: null | string,
1818
captured: null | string,
19+
skipBubbling?: ?boolean,
1920
|},
2021
registrationName?: string,
2122
|};
@@ -24,6 +25,7 @@ export type CustomDispatchConfig = {|
2425
phasedRegistrationNames: {|
2526
bubbled: null,
2627
captured: null,
28+
skipBubbling?: ?boolean,
2729
|},
2830
registrationName?: string,
2931
customEvent: true,

scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const customBubblingEventTypes: {
1919
phasedRegistrationNames: $ReadOnly<{|
2020
captured: string,
2121
bubbled: string,
22+
skipBubbling?: ?boolean,
2223
|}>,
2324
|}>,
2425
...,

0 commit comments

Comments
 (0)