mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
React events: fix nested Hover components error (#15428)
* Add failing test for nested Hover * Fix error caused by nested Hover event components
This commit is contained in:
Vendored
+1
@@ -20,6 +20,7 @@ type FocusProps = {
|
||||
onBlur: (e: FocusEvent) => void,
|
||||
onFocus: (e: FocusEvent) => void,
|
||||
onFocusChange: boolean => void,
|
||||
stopPropagation: boolean,
|
||||
};
|
||||
|
||||
type FocusState = {
|
||||
|
||||
Vendored
+24
-16
@@ -23,6 +23,8 @@ type HoverProps = {
|
||||
onHoverEnd: (e: HoverEvent) => void,
|
||||
onHoverMove: (e: HoverEvent) => void,
|
||||
onHoverStart: (e: HoverEvent) => void,
|
||||
preventDefault: boolean,
|
||||
stopPropagation: boolean,
|
||||
};
|
||||
|
||||
type HoverState = {
|
||||
@@ -178,6 +180,11 @@ function dispatchHoverEndEvents(
|
||||
if (props.onHoverChange) {
|
||||
dispatchHoverChangeEvent(context, props, state);
|
||||
}
|
||||
|
||||
state.isInHitSlop = false;
|
||||
state.hoverTarget = null;
|
||||
state.skipMouseAfterPointer = false;
|
||||
state.isTouched = false;
|
||||
};
|
||||
|
||||
if (state.isActiveHovered) {
|
||||
@@ -231,7 +238,8 @@ const HoverResponder = {
|
||||
props: HoverProps,
|
||||
state: HoverState,
|
||||
): boolean {
|
||||
const {type, phase, target, nativeEvent} = event;
|
||||
const {type, phase, target} = event;
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
|
||||
// Hover doesn't handle capture target events at this point
|
||||
if (phase === CAPTURE_PHASE) {
|
||||
@@ -247,11 +255,18 @@ const HoverResponder = {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'touchcancel':
|
||||
case 'touchend': {
|
||||
if (state.isTouched) {
|
||||
state.isTouched = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pointerover':
|
||||
case 'mouseover': {
|
||||
if (!state.isHovered && !state.isTouched) {
|
||||
if ((nativeEvent: any).pointerType === 'touch') {
|
||||
if (nativeEvent.pointerType === 'touch') {
|
||||
state.isTouched = true;
|
||||
return false;
|
||||
}
|
||||
@@ -261,8 +276,8 @@ const HoverResponder = {
|
||||
if (
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
target.ownerDocument,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
nativeEvent.x,
|
||||
nativeEvent.y,
|
||||
)
|
||||
) {
|
||||
state.isInHitSlop = true;
|
||||
@@ -278,10 +293,6 @@ const HoverResponder = {
|
||||
if (state.isHovered && !state.isTouched) {
|
||||
dispatchHoverEndEvents(event, context, props, state);
|
||||
}
|
||||
state.isInHitSlop = false;
|
||||
state.hoverTarget = null;
|
||||
state.isTouched = false;
|
||||
state.skipMouseAfterPointer = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -296,8 +307,8 @@ const HoverResponder = {
|
||||
if (
|
||||
!context.isPositionWithinTouchHitTarget(
|
||||
target.ownerDocument,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
nativeEvent.x,
|
||||
nativeEvent.y,
|
||||
)
|
||||
) {
|
||||
dispatchHoverStartEvents(event, context, props, state);
|
||||
@@ -307,18 +318,15 @@ const HoverResponder = {
|
||||
if (
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
target.ownerDocument,
|
||||
(nativeEvent: any).x,
|
||||
(nativeEvent: any).y,
|
||||
nativeEvent.x,
|
||||
nativeEvent.y,
|
||||
)
|
||||
) {
|
||||
dispatchHoverEndEvents(event, context, props, state);
|
||||
state.isInHitSlop = true;
|
||||
} else {
|
||||
if (props.onHoverMove) {
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hovermove',
|
||||
event.target,
|
||||
);
|
||||
const syntheticEvent = createHoverEvent('hovermove', target);
|
||||
context.dispatchEvent(syntheticEvent, props.onHoverMove, {
|
||||
discrete: false,
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('Focus event responder', () => {
|
||||
});
|
||||
|
||||
describe('nested Focus components', () => {
|
||||
it('does not propagate events by default', () => {
|
||||
it('do not propagate events by default', () => {
|
||||
const events = [];
|
||||
const innerRef = React.createRef();
|
||||
const outerRef = React.createRef();
|
||||
|
||||
@@ -14,9 +14,14 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let Hover;
|
||||
|
||||
const createPointerEvent = type => {
|
||||
const event = document.createEvent('Event');
|
||||
event.initEvent(type, true, true);
|
||||
const createPointerEvent = (type, data) => {
|
||||
const event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true);
|
||||
if (data != null) {
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
event[key] = value;
|
||||
});
|
||||
}
|
||||
return event;
|
||||
};
|
||||
|
||||
@@ -361,6 +366,63 @@ describe('Hover event responder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested Hover components', () => {
|
||||
it('do not propagate events by default', () => {
|
||||
const events = [];
|
||||
const innerRef = React.createRef();
|
||||
const outerRef = React.createRef();
|
||||
const createEventHandler = msg => () => {
|
||||
events.push(msg);
|
||||
};
|
||||
|
||||
const element = (
|
||||
<Hover
|
||||
onHoverStart={createEventHandler('outer: onHoverStart')}
|
||||
onHoverEnd={createEventHandler('outer: onHoverEnd')}
|
||||
onHoverChange={createEventHandler('outer: onHoverChange')}>
|
||||
<div ref={outerRef}>
|
||||
<Hover
|
||||
onHoverStart={createEventHandler('inner: onHoverStart')}
|
||||
onHoverEnd={createEventHandler('inner: onHoverEnd')}
|
||||
onHoverChange={createEventHandler('inner: onHoverChange')}>
|
||||
<div ref={innerRef} />
|
||||
</Hover>
|
||||
</div>
|
||||
</Hover>
|
||||
);
|
||||
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
outerRef.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
outerRef.current.dispatchEvent(
|
||||
createPointerEvent('pointerout', {relatedTarget: innerRef.current}),
|
||||
);
|
||||
innerRef.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
innerRef.current.dispatchEvent(
|
||||
createPointerEvent('pointerout', {relatedTarget: outerRef.current}),
|
||||
);
|
||||
outerRef.current.dispatchEvent(
|
||||
createPointerEvent('pointerover', {relatedTarget: innerRef.current}),
|
||||
);
|
||||
outerRef.current.dispatchEvent(createPointerEvent('pointerout'));
|
||||
// TODO: correct result should include commented events
|
||||
expect(events).toEqual([
|
||||
'outer: onHoverStart',
|
||||
'outer: onHoverChange',
|
||||
// 'outer: onHoverEnd',
|
||||
// 'outer: onHoverChange',
|
||||
'inner: onHoverStart',
|
||||
'inner: onHoverChange',
|
||||
'inner: onHoverEnd',
|
||||
'inner: onHoverChange',
|
||||
// 'outer: onHoverStart',
|
||||
// 'outer: onHoverChange',
|
||||
'outer: onHoverEnd',
|
||||
'outer: onHoverChange',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('expect displayName to show up for event component', () => {
|
||||
expect(Hover.displayName).toBe('Hover');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user