Skip to content

Commit

Permalink
Experimental Event API: add rootEventTypes support to event respond…
Browse files Browse the repository at this point in the history
…ers (#15475)

* Adds rootEventTypes
  • Loading branch information
trueadm authored Apr 23, 2019
1 parent 784ebd8 commit 017d6f1
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/react-dom/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,7 @@ export function listenToEventResponderEventTypes(
if (__DEV__) {
warning(
typeof targetEventType === 'object' && targetEventType !== null,
'Event Responder: invalid entry in targetEventTypes array. ' +
'Event Responder: invalid entry in event types array. ' +
'Entry must be string or an object. Instead, got %s.',
targetEventType,
);
Expand Down
17 changes: 13 additions & 4 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
setEnabled as ReactBrowserEventEmitterSetEnabled,
} from '../events/ReactBrowserEventEmitter';
import {Namespaces, getChildNamespace} from '../shared/DOMNamespaces';
import {addRootEventTypesForComponentInstance} from '../events/DOMEventResponderSystem';
import {
ELEMENT_NODE,
TEXT_NODE,
Expand Down Expand Up @@ -906,10 +907,18 @@ export function updateEventComponent(
if (enableEventAPI) {
const rootContainerInstance = ((eventComponentInstance.rootInstance: any): Container);
const rootElement = rootContainerInstance.ownerDocument;
listenToEventResponderEventTypes(
eventComponentInstance.responder.targetEventTypes,
rootElement,
);
const responder = eventComponentInstance.responder;
const {rootEventTypes, targetEventTypes} = responder;
if (targetEventTypes !== undefined) {
listenToEventResponderEventTypes(targetEventTypes, rootElement);
}
if (rootEventTypes !== undefined) {
addRootEventTypesForComponentInstance(
eventComponentInstance,
rootEventTypes,
);
listenToEventResponderEventTypes(rootEventTypes, rootElement);
}
}
}

Expand Down
63 changes: 61 additions & 2 deletions packages/react-dom/src/events/DOMEventResponderSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,20 @@ const eventResponderContext: ReactResponderContext = {
rootEventComponentInstances,
);
}
rootEventComponentInstances.add(
((currentInstance: any): ReactEventComponentInstance),
const componentInstance = ((currentInstance: any): ReactEventComponentInstance);
let rootEventTypesSet = componentInstance.rootEventTypes;
if (rootEventTypesSet === null) {
rootEventTypesSet = componentInstance.rootEventTypes = new Set();
}
invariant(
!rootEventTypesSet.has(topLevelEventType),
'addRootEventTypes() found a duplicate root event ' +
'type of "%s". This might be because the event type exists in the event responder "rootEventTypes" ' +
'array or because of a previous addRootEventTypes() using this root event type.',
rootEventType,
);
rootEventTypesSet.add(topLevelEventType);
rootEventComponentInstances.add(componentInstance);
}
},
removeRootEventTypes(
Expand All @@ -222,6 +233,11 @@ const eventResponderContext: ReactResponderContext = {
let rootEventComponents = rootEventTypesToEventComponentInstances.get(
topLevelEventType,
);
let rootEventTypesSet = ((currentInstance: any): ReactEventComponentInstance)
.rootEventTypes;
if (rootEventTypesSet !== null) {
rootEventTypesSet.delete(topLevelEventType);
}
if (rootEventComponents !== undefined) {
rootEventComponents.delete(
((currentInstance: any): ReactEventComponentInstance),
Expand Down Expand Up @@ -636,6 +652,20 @@ export function unmountEventResponder(
if (responder.onOwnershipChange !== undefined) {
ownershipChangeListeners.delete(eventComponentInstance);
}
const rootEventTypesSet = eventComponentInstance.rootEventTypes;
if (rootEventTypesSet !== null) {
const rootEventTypes = Array.from(rootEventTypesSet);

for (let i = 0; i < rootEventTypes.length; i++) {
const topLevelEventType = rootEventTypes[i];
let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
topLevelEventType,
);
if (rootEventComponentInstances !== undefined) {
rootEventComponentInstances.delete(eventComponentInstance);
}
}
}
}

function validateResponderContext(): void {
Expand Down Expand Up @@ -671,3 +701,32 @@ export function dispatchEventForResponderEventSystem(
}
}
}

export function addRootEventTypesForComponentInstance(
eventComponentInstance: ReactEventComponentInstance,
rootEventTypes: Array<ReactEventResponderEventType>,
): void {
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const topLevelEventType =
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
topLevelEventType,
);
if (rootEventComponentInstances === undefined) {
rootEventComponentInstances = new Set();
rootEventTypesToEventComponentInstances.set(
topLevelEventType,
rootEventComponentInstances,
);
}
let rootEventTypesSet = eventComponentInstance.rootEventTypes;
if (rootEventTypesSet === null) {
rootEventTypesSet = eventComponentInstance.rootEventTypes = new Set();
}
rootEventTypesSet.add(topLevelEventType);
rootEventComponentInstances.add(
((eventComponentInstance: any): ReactEventComponentInstance),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ let ReactSymbols;

function createReactEventComponent(
targetEventTypes,
rootEventTypes,
createInitialState,
onEvent,
onEventCapture,
Expand All @@ -26,6 +27,7 @@ function createReactEventComponent(
) {
const testEventResponder = {
targetEventTypes,
rootEventTypes,
createInitialState,
onEvent,
onEventCapture,
Expand Down Expand Up @@ -90,6 +92,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventResponderFiredCount++;
eventLog.push({
Expand Down Expand Up @@ -163,6 +166,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventLog.push({
name: event.type,
Expand Down Expand Up @@ -217,6 +221,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventResponderFiredCount++;
eventLog.push({
Expand Down Expand Up @@ -288,6 +293,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponentA = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventLog.push(`A [bubble]`);
},
Expand All @@ -299,6 +305,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponentB = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventLog.push(`B [bubble]`);
},
Expand Down Expand Up @@ -336,6 +343,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventLog.push(`${props.name} [bubble]`);
},
Expand Down Expand Up @@ -377,6 +385,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
eventLog.push(`${props.name} [bubble]`);
},
Expand Down Expand Up @@ -413,6 +422,7 @@ describe('DOMEventResponderSystem', () => {
const ClickEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
if (props.onMagicClick) {
const syntheticEvent = {
Expand Down Expand Up @@ -505,6 +515,7 @@ describe('DOMEventResponderSystem', () => {
const LongPressEventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props) => {
handleEvent(event, context, props, 'bubble');
},
Expand Down Expand Up @@ -551,6 +562,7 @@ describe('DOMEventResponderSystem', () => {
undefined,
undefined,
undefined,
undefined,
(event, context, props, state) => {},
() => {
onUnmountFired++;
Expand All @@ -573,6 +585,7 @@ describe('DOMEventResponderSystem', () => {

const EventComponent = createReactEventComponent(
[],
undefined,
() => ({
incrementAmount: 5,
}),
Expand Down Expand Up @@ -603,6 +616,7 @@ describe('DOMEventResponderSystem', () => {
const EventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props, state) => {
ownershipGained = context.requestOwnership();
},
Expand Down Expand Up @@ -641,6 +655,7 @@ describe('DOMEventResponderSystem', () => {
const EventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props, state) => {
queryResult = Array.from(
context.getEventTargetsFromTarget(event.target),
Expand Down Expand Up @@ -696,6 +711,7 @@ describe('DOMEventResponderSystem', () => {
const EventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props, state) => {
queryResult = context.getEventTargetsFromTarget(
event.target,
Expand Down Expand Up @@ -743,6 +759,7 @@ describe('DOMEventResponderSystem', () => {
const EventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props, state) => {
queryResult = context.getEventTargetsFromTarget(
event.target,
Expand Down Expand Up @@ -795,6 +812,7 @@ describe('DOMEventResponderSystem', () => {
const EventComponent = createReactEventComponent(
['click'],
undefined,
undefined,
(event, context, props, state) => {
queryResult = context.getEventTargetsFromTarget(
event.target,
Expand Down Expand Up @@ -855,4 +873,48 @@ describe('DOMEventResponderSystem', () => {
]);
expect(queryResult3).toEqual([]);
});

it('the event responder root listeners should fire on a root click event', () => {
let eventResponderFiredCount = 0;
let eventLog = [];

const ClickEventComponent = createReactEventComponent(
undefined,
['click'],
undefined,
undefined,
undefined,
event => {
eventResponderFiredCount++;
eventLog.push({
name: event.type,
passive: event.passive,
passiveSupported: event.passiveSupported,
phase: 'root',
});
},
);

const Test = () => (
<ClickEventComponent>
<button>Click me!</button>
</ClickEventComponent>
);

ReactDOM.render(<Test />, container);
expect(container.innerHTML).toBe('<button>Click me!</button>');

// Clicking the button should trigger the event responder onEvent() twice
dispatchClickEvent(document.body);
expect(eventResponderFiredCount).toBe(1);
expect(eventLog.length).toBe(1);
expect(eventLog).toEqual([
{
name: 'click',
passive: false,
passiveSupported: false,
phase: 'root',
},
]);
});
});
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,7 @@ function completeWork(
context: null,
props: newProps,
responder,
rootEventTypes: null,
rootInstance: rootContainerInstance,
state: responderState,
};
Expand Down
4 changes: 3 additions & 1 deletion packages/shared/ReactTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export type ReactEventResponderEventType =
| {name: string, passive?: boolean, capture?: boolean};

export type ReactEventResponder = {
targetEventTypes: Array<ReactEventResponderEventType>,
targetEventTypes?: Array<ReactEventResponderEventType>,
rootEventTypes?: Array<ReactEventResponderEventType>,
createInitialState?: (props: null | Object) => Object,
stopLocalPropagation: boolean,
onEvent?: (
Expand Down Expand Up @@ -123,6 +124,7 @@ export type ReactEventComponentInstance = {|
context: null | Object,
props: null | Object,
responder: ReactEventResponder,
rootEventTypes: null | Set<string>,
rootInstance: mixed,
state: null | Object,
|};
Expand Down

0 comments on commit 017d6f1

Please sign in to comment.