Ensure TouchHitTarget element is server side rendered with hit slop (#15385)

* Follow up to 15381

* Add back in hit slop properties

* Prettier

* Fix lint

* move hydration update out of DEV block

* Remove pointer-events:auto
This commit is contained in:
Dominic Gannaway 2019-05-06 20:13:23 +01:00 committed by GitHub
parent 2e5d1a8b9e
commit d38cfd452f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 16 deletions

View File

@ -97,6 +97,7 @@ let didWarnShadyDOM = false;
const DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
const SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
const HYDRATE_TOUCH_HIT_TARGET = 'hydrateTouchHitTarget';
const AUTOFOCUS = 'autoFocus';
const CHILDREN = 'children';
const STYLE = 'style';
@ -1031,6 +1032,8 @@ export function diffHydratedProperties(
}
ensureListeningTo(rootContainerElement, propKey);
}
} else if (enableEventAPI && propKey === HYDRATE_TOUCH_HIT_TARGET) {
updatePayload = [STYLE, rawProps.style];
} else if (
__DEV__ &&
// Convince Flow we've calculated it (it's DEV-only in this method.)

View File

@ -949,11 +949,14 @@ export function getEventTargetChildElement(
style: {
position: 'absolute',
zIndex: -1,
pointerEvents: null,
bottom: bottom ? `-${bottom}px` : '0px',
left: left ? `-${left}px` : '0px',
right: right ? `-${right}px` : '0px',
top: top ? `-${top}px` : '0px',
},
hydrateTouchHitTarget: true,
suppressHydrationWarning: true,
},
};
}

View File

@ -1173,15 +1173,24 @@ class ReactDOMServerRenderer {
elementType.$$typeof === REACT_EVENT_TARGET_TYPE &&
elementType.type === REACT_EVENT_TARGET_TOUCH_HIT
) {
// We do not render a hit slop element anymore. Instead we rely
// on hydration adding in the hit slop element. The previous
// logic had a bug where rendering a hit slop at SSR meant that
// mouse events incorrectly registered events on the hit slop
// even though it designed to be used for touch events only.
// The logic that filters out mouse events from the hit slop
// is handled in event responder modules, which only get
// initialized upon hydration.
return '';
const props = nextElement.props;
const bottom = props.bottom || 0;
const left = props.left || 0;
const right = props.right || 0;
const top = props.top || 0;
if (bottom === 0 && left === 0 && right === 0 && top === 0) {
return '';
}
let topString = top ? `-${top}px` : '0px';
let leftString = left ? `-${left}px` : '0px';
let rightString = right ? `-${right}px` : '0x';
let bottomString = bottom ? `-${bottom}px` : '0px';
return (
`<div style="position:absolute;pointer-events:none;z-index:-1;bottom:` +
`${bottomString};left:${leftString};right:${rightString};top:${topString}"></div>`
);
}
const nextChildren = toArray(
((nextChild: any): ReactElement).props.children,

View File

@ -219,6 +219,7 @@ const properties = {};
'suppressContentEditableWarning',
'suppressHydrationWarning',
'style',
'hydrateTouchHitTarget',
].forEach(name => {
properties[name] = new PropertyInfoRecord(
name,

View File

@ -512,9 +512,46 @@ describe('TouchHitTarget', () => {
ReactDOM.render(<Test />, container);
expect(Scheduler).toFlushWithoutYielding();
expect(container.innerHTML).toBe(
'<div style="position: relative; z-index: 0;"><span>Random span 1</span>' +
'<div style="position: absolute; z-index: -1; bottom: -10px; ' +
'left: 0px; right: -10px; top: -10px;"></div><span>Random span 2</span></div>',
'<div style="position: relative; z-index: 0;"><span>Random span 1</span><div style="position: absolute; ' +
'z-index: -1; bottom: -10px; left: 0px; right: -10px; top: -10px;">' +
'</div><span>Random span 2</span></div>',
);
});
it('should hydrate TouchHitTarget hit slop elements correcty', () => {
const Test = () => (
<EventComponent>
<div style={{position: 'relative', zIndex: 0}}>
<TouchHitTarget />
</div>
</EventComponent>
);
const container = document.createElement('div');
container.innerHTML = '<div style="position:relative;z-index:0"></div>';
ReactDOM.hydrate(<Test />, container);
expect(Scheduler).toFlushWithoutYielding();
expect(container.innerHTML).toBe(
'<div style="position:relative;z-index:0"></div>',
);
const Test2 = () => (
<EventComponent>
<div style={{position: 'relative', zIndex: 0}}>
<TouchHitTarget top={10} left={10} right={10} bottom={10} />
</div>
</EventComponent>
);
const container2 = document.createElement('div');
container2.innerHTML =
'<div style="position:relative;z-index:0"><div style="position:absolute;pointer-events:none;z-index:-1;' +
'bottom:-10px;left:-10px;right:-10px;top:-10px"></div></div>';
ReactDOM.hydrate(<Test2 />, container2);
expect(Scheduler).toFlushWithoutYielding();
expect(container2.innerHTML).toBe(
'<div style="position:relative;z-index:0"><div style="position: absolute; z-index: -1; ' +
'bottom: -10px; left: -10px; right: -10px; top: -10px;"></div></div>',
);
});
@ -565,7 +602,7 @@ describe('TouchHitTarget', () => {
expect(output).toBe('<div></div>');
});
it('should render a TouchHitTarget without hit slop values', () => {
it('should render a TouchHitTarget with hit slop values', () => {
const Test = () => (
<EventComponent>
<div>
@ -575,7 +612,10 @@ describe('TouchHitTarget', () => {
);
let output = ReactDOMServer.renderToString(<Test />);
expect(output).toBe('<div></div>');
expect(output).toBe(
'<div><div style="position:absolute;pointer-events:none;z-index:-1;' +
'bottom:-10px;left:-10px;right:-10px;top:-10px"></div></div>',
);
const Test2 = () => (
<EventComponent>
@ -586,7 +626,10 @@ describe('TouchHitTarget', () => {
);
output = ReactDOMServer.renderToString(<Test2 />);
expect(output).toBe('<div></div>');
expect(output).toBe(
'<div><div style="position:absolute;pointer-events:none;z-index:-1;' +
'bottom:-10px;left:0px;right:0x;top:0px"></div></div>',
);
const Test3 = () => (
<EventComponent>
@ -597,7 +640,10 @@ describe('TouchHitTarget', () => {
);
output = ReactDOMServer.renderToString(<Test3 />);
expect(output).toBe('<div></div>');
expect(output).toBe(
'<div><div style="position:absolute;pointer-events:none;z-index:-1;' +
'bottom:-4px;left:-2px;right:-3px;top:-1px"></div></div>',
);
});
});
});