mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Add skipBubbling property to dispatch config (#23366)
This commit is contained in:
@@ -89,7 +89,12 @@ function getParent(inst) {
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
|
||||
export function traverseTwoPhase(
|
||||
inst: Object,
|
||||
fn: Function,
|
||||
arg: Function,
|
||||
skipBubbling: boolean,
|
||||
) {
|
||||
const path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
@@ -99,14 +104,24 @@ export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
|
||||
for (i = path.length; i-- > 0; ) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
if (skipBubbling) {
|
||||
// Dispatch on target only
|
||||
fn(path[0], 'bubbled', arg);
|
||||
} else {
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateTwoPhaseDispatchesSingle(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
|
||||
traverseTwoPhase(
|
||||
event._targetInst,
|
||||
accumulateDirectionalDispatches,
|
||||
event,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +129,17 @@ function accumulateTwoPhaseDispatches(events) {
|
||||
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
|
||||
}
|
||||
|
||||
function accumulateCapturePhaseDispatches(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
traverseTwoPhase(
|
||||
event._targetInst,
|
||||
accumulateDirectionalDispatches,
|
||||
event,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
@@ -178,7 +204,15 @@ const ReactNativeBridgeEventPlugin = {
|
||||
nativeEventTarget,
|
||||
);
|
||||
if (bubbleDispatchConfig) {
|
||||
accumulateTwoPhaseDispatches(event);
|
||||
const skipBubbling =
|
||||
event != null &&
|
||||
event.dispatchConfig.phasedRegistrationNames != null &&
|
||||
event.dispatchConfig.phasedRegistrationNames.skipBubbling;
|
||||
if (skipBubbling) {
|
||||
accumulateCapturePhaseDispatches(event);
|
||||
} else {
|
||||
accumulateTwoPhaseDispatches(event);
|
||||
}
|
||||
} else if (directDispatchConfig) {
|
||||
accumulateDirectDispatches(event);
|
||||
} else {
|
||||
|
||||
@@ -73,6 +73,7 @@ export type ViewConfig = $ReadOnly<{
|
||||
phasedRegistrationNames: $ReadOnly<{
|
||||
captured: string,
|
||||
bubbled: string,
|
||||
skipBubble?: ?boolean,
|
||||
}>,
|
||||
}>,
|
||||
...,
|
||||
|
||||
@@ -633,6 +633,106 @@ describe('ReactFabric', () => {
|
||||
expect(touchStart2).toBeCalled();
|
||||
});
|
||||
|
||||
describe('skipBubbling', () => {
|
||||
it('should skip bubbling to ancestor if specified', () => {
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTView',
|
||||
bubblingEventTypes: {
|
||||
topDefaultBubblingEvent: {
|
||||
phasedRegistrationNames: {
|
||||
captured: 'onDefaultBubblingEventCapture',
|
||||
bubbled: 'onDefaultBubblingEvent',
|
||||
},
|
||||
},
|
||||
topBubblingEvent: {
|
||||
phasedRegistrationNames: {
|
||||
captured: 'onBubblingEventCapture',
|
||||
bubbled: 'onBubblingEvent',
|
||||
skipBubbling: false,
|
||||
},
|
||||
},
|
||||
topSkipBubblingEvent: {
|
||||
phasedRegistrationNames: {
|
||||
captured: 'onSkippedBubblingEventCapture',
|
||||
bubbled: 'onSkippedBubblingEvent',
|
||||
skipBubbling: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
const ancestorBubble = jest.fn();
|
||||
const ancestorCapture = jest.fn();
|
||||
const targetBubble = jest.fn();
|
||||
const targetCapture = jest.fn();
|
||||
|
||||
const event = {};
|
||||
|
||||
act(() => {
|
||||
ReactFabric.render(
|
||||
<View
|
||||
onSkippedBubblingEventCapture={ancestorCapture}
|
||||
onDefaultBubblingEventCapture={ancestorCapture}
|
||||
onBubblingEventCapture={ancestorCapture}
|
||||
onSkippedBubblingEvent={ancestorBubble}
|
||||
onDefaultBubblingEvent={ancestorBubble}
|
||||
onBubblingEvent={ancestorBubble}>
|
||||
<View
|
||||
onSkippedBubblingEventCapture={targetCapture}
|
||||
onDefaultBubblingEventCapture={targetCapture}
|
||||
onBubblingEventCapture={targetCapture}
|
||||
onSkippedBubblingEvent={targetBubble}
|
||||
onDefaultBubblingEvent={targetBubble}
|
||||
onBubblingEvent={targetBubble}
|
||||
/>
|
||||
</View>,
|
||||
11,
|
||||
);
|
||||
});
|
||||
|
||||
expect(nativeFabricUIManager.createNode.mock.calls.length).toBe(2);
|
||||
expect(nativeFabricUIManager.registerEventHandler.mock.calls.length).toBe(
|
||||
1,
|
||||
);
|
||||
const [
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
childInstance,
|
||||
] = nativeFabricUIManager.createNode.mock.calls[0];
|
||||
const [
|
||||
dispatchEvent,
|
||||
] = nativeFabricUIManager.registerEventHandler.mock.calls[0];
|
||||
|
||||
dispatchEvent(childInstance, 'topDefaultBubblingEvent', event);
|
||||
expect(targetBubble).toHaveBeenCalledTimes(1);
|
||||
expect(targetCapture).toHaveBeenCalledTimes(1);
|
||||
expect(ancestorCapture).toHaveBeenCalledTimes(1);
|
||||
expect(ancestorBubble).toHaveBeenCalledTimes(1);
|
||||
ancestorBubble.mockReset();
|
||||
ancestorCapture.mockReset();
|
||||
targetBubble.mockReset();
|
||||
targetCapture.mockReset();
|
||||
|
||||
dispatchEvent(childInstance, 'topBubblingEvent', event);
|
||||
expect(targetBubble).toHaveBeenCalledTimes(1);
|
||||
expect(targetCapture).toHaveBeenCalledTimes(1);
|
||||
expect(ancestorCapture).toHaveBeenCalledTimes(1);
|
||||
expect(ancestorBubble).toHaveBeenCalledTimes(1);
|
||||
ancestorBubble.mockReset();
|
||||
ancestorCapture.mockReset();
|
||||
targetBubble.mockReset();
|
||||
targetCapture.mockReset();
|
||||
|
||||
dispatchEvent(childInstance, 'topSkipBubblingEvent', event);
|
||||
expect(targetBubble).toHaveBeenCalledTimes(1);
|
||||
expect(targetCapture).toHaveBeenCalledTimes(1);
|
||||
expect(ancestorCapture).toHaveBeenCalledTimes(1);
|
||||
expect(ancestorBubble).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches event with target as instance', () => {
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {
|
||||
|
||||
@@ -16,6 +16,7 @@ export type DispatchConfig = {|
|
||||
phasedRegistrationNames: {|
|
||||
bubbled: null | string,
|
||||
captured: null | string,
|
||||
skipBubbling?: ?boolean,
|
||||
|},
|
||||
registrationName?: string,
|
||||
|};
|
||||
@@ -24,6 +25,7 @@ export type CustomDispatchConfig = {|
|
||||
phasedRegistrationNames: {|
|
||||
bubbled: null,
|
||||
captured: null,
|
||||
skipBubbling?: ?boolean,
|
||||
|},
|
||||
registrationName?: string,
|
||||
customEvent: true,
|
||||
|
||||
@@ -19,6 +19,7 @@ const customBubblingEventTypes: {
|
||||
phasedRegistrationNames: $ReadOnly<{|
|
||||
captured: string,
|
||||
bubbled: string,
|
||||
skipBubbling?: ?boolean,
|
||||
|}>,
|
||||
|}>,
|
||||
...,
|
||||
|
||||
Reference in New Issue
Block a user