Event API: ensure preventDefault works for nested targets (#15633)
Dominic Gannaway committed
May 13, 2019 at 13:45 UTC
fec74f99daebd07c38a647941afa6bf0e0b2306e
4 files changed
+46
-5
packages/react-dom/src/events/DOMEventResponderSystem.js
+22
@@ -208,6 +208,7 @@ const eventResponderContext: ReactResponderContext = {
208
childTarget: Element | Document,
209
parentTarget: Element | Document,
210
): boolean {
211
+ validateResponderContext();
212
const childFiber = getClosestInstanceFromNode(childTarget);
213
const parentFiber = getClosestInstanceFromNode(parentTarget);
214
@@ -345,6 +346,7 @@ const eventResponderContext: ReactResponderContext = {
346
}
347
},
348
getFocusableElementsInScope(): Array<HTMLElement> {
349
+ validateResponderContext();
350
const focusableElements = [];
351
const eventComponentInstance = ((currentInstance: any): ReactEventComponentInstance);
352
let node = ((eventComponentInstance.currentFiber: any): Fiber).child;
@@ -383,6 +385,7 @@ const eventResponderContext: ReactResponderContext = {
385
getEventPointerType(
386
event: ReactResponderEvent,
387
): '' | 'mouse' | 'keyboard' | 'pen' | 'touch' {
388
+ validateResponderContext();
389
const nativeEvent: any = event.nativeEvent;
390
const {type, pointerType} = nativeEvent;
391
if (pointerType != null) {
@@ -400,6 +403,7 @@ const eventResponderContext: ReactResponderContext = {
403
return '';
404
},
405
getEventCurrentTarget(event: ReactResponderEvent): Element {
406
+ validateResponderContext();
407
const target: any = event.target;
408
let currentTarget = target;
409
while (
@@ -412,8 +416,26 @@ const eventResponderContext: ReactResponderContext = {
416
return currentTarget;
417
},
418
getTimeStamp(): number {
419
+ validateResponderContext();
420
return currentTimeStamp;
421
},
422
+ isTargetWithinHostComponent(
423
+ target: Element | Document,
424
+ elementType: string,
425
+ ): boolean {
426
+ validateResponderContext();
427
+ let fiber = getClosestInstanceFromNode(target);
428
+ while (fiber !== null) {
429
+ if (fiber.stateNode === currentInstance) {
430
+ return false;
431
+ }
432
+ if (fiber.tag === HostComponent && fiber.type === elementType) {
433
+ return true;
434
+ }
435
+ fiber = fiber.return;
436
+ }
437
+ return false;
438
+ },
439
};
440
441
function isTargetWithinEventComponent(target: Element | Document): boolean {
packages/react-events/src/Press.js
+1
-5
@@ -418,10 +418,6 @@ function dispatchCancel(
418
}
419
}
420
421
-function isAnchorTagElement(eventTarget: EventTarget): boolean {
422
- return (eventTarget: any).nodeName === 'A';
423
-}
424
-
421
function isValidKeyPress(key: string): boolean {
422
// Accessibility for keyboards. Space and Enter only.
423
return key === ' ' || key === 'Enter';
@@ -673,7 +669,7 @@ const PressResponder = {
669
}
670
671
case 'click': {
676
- if (isAnchorTagElement(target)) {
672
+ if (context.isTargetWithinHostComponent(target, 'a')) {
673
const {
674
altKey,
675
ctrlKey,
packages/react-events/src/__tests__/Press-test.internal.js
+19
@@ -1965,6 +1965,25 @@ describe('Event responder: Press', () => {
1965
expect(preventDefault).toBeCalled();
1966
});
1967
1968
+ it('prevents native behaviour by default with nested elements', () => {
1969
+ const onPress = jest.fn();
1970
+ const preventDefault = jest.fn();
1971
+ const ref = React.createRef();
1972
+ const element = (
1973
+ <Press onPress={onPress}>
1974
+ <a href="#">
1975
+ <div ref={ref} />
1976
+ </a>
1977
+ </Press>
1978
+ );
1979
+ ReactDOM.render(element, container);
1980
+
1981
+ ref.current.dispatchEvent(createEvent('pointerdown'));
1982
+ ref.current.dispatchEvent(createEvent('pointerup'));
1983
+ ref.current.dispatchEvent(createEvent('click', {preventDefault}));
1984
+ expect(preventDefault).toBeCalled();
1985
+ });
1986
+
1987
it('uses native behaviour for interactions with modifier keys', () => {
1988
const onPress = jest.fn();
1989
const preventDefault = jest.fn();
packages/shared/ReactTypes.js
+4
@@ -194,4 +194,8 @@ export type ReactResponderContext = {
194
): '' | 'mouse' | 'keyboard' | 'pen' | 'touch',
195
getEventCurrentTarget(event: ReactResponderEvent): Element,
196
getTimeStamp: () => number,
197
+ isTargetWithinHostComponent: (
198
+ target: Element | Document,
199
+ elementType: string,
200
+ ) => boolean,
201
};