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
Dominic Gannaway committed
May 6, 2019 at 20:13 UTC
d38cfd452f014167d223c73091d4a148a9f893a5
5 files changed
+78
-16
packages/react-dom/src/client/ReactDOMComponent.js
+3
@@ -97,6 +97,7 @@ let didWarnShadyDOM = false;
97
const DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
98
const SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
99
const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
100
+const HYDRATE_TOUCH_HIT_TARGET = 'hydrateTouchHitTarget';
101
const AUTOFOCUS = 'autoFocus';
102
const CHILDREN = 'children';
103
const STYLE = 'style';
@@ -1031,6 +1032,8 @@ export function diffHydratedProperties(
1032
}
1033
ensureListeningTo(rootContainerElement, propKey);
1034
}
1035
+ } else if (enableEventAPI && propKey === HYDRATE_TOUCH_HIT_TARGET) {
1036
+ updatePayload = [STYLE, rawProps.style];
1037
} else if (
1038
__DEV__ &&
1039
// Convince Flow we've calculated it (it's DEV-only in this method.)
packages/react-dom/src/client/ReactDOMHostConfig.js
+3
@@ -949,11 +949,14 @@ export function getEventTargetChildElement(
949
style: {
950
position: 'absolute',
951
zIndex: -1,
952
+ pointerEvents: null,
953
bottom: bottom ? `-${bottom}px` : '0px',
954
left: left ? `-${left}px` : '0px',
955
right: right ? `-${right}px` : '0px',
956
top: top ? `-${top}px` : '0px',
957
},
958
+ hydrateTouchHitTarget: true,
959
+ suppressHydrationWarning: true,
960
},
961
};
962
}
packages/react-dom/src/server/ReactPartialRenderer.js
+18
-9
@@ -1173,15 +1173,24 @@ class ReactDOMServerRenderer {
1173
elementType.$$typeof === REACT_EVENT_TARGET_TYPE &&
1174
elementType.type === REACT_EVENT_TARGET_TOUCH_HIT
1175
) {
1176
- // We do not render a hit slop element anymore. Instead we rely
1177
- // on hydration adding in the hit slop element. The previous
1178
- // logic had a bug where rendering a hit slop at SSR meant that
1179
- // mouse events incorrectly registered events on the hit slop
1180
- // even though it designed to be used for touch events only.
1181
- // The logic that filters out mouse events from the hit slop
1182
- // is handled in event responder modules, which only get
1183
- // initialized upon hydration.
1184
- return '';
1176
+ const props = nextElement.props;
1177
+ const bottom = props.bottom || 0;
1178
+ const left = props.left || 0;
1179
+ const right = props.right || 0;
1180
+ const top = props.top || 0;
1181
+
1182
+ if (bottom === 0 && left === 0 && right === 0 && top === 0) {
1183
+ return '';
1184
+ }
1185
+ let topString = top ? `-${top}px` : '0px';
1186
+ let leftString = left ? `-${left}px` : '0px';
1187
+ let rightString = right ? `-${right}px` : '0x';
1188
+ let bottomString = bottom ? `-${bottom}px` : '0px';
1189
+
1190
+ return (
1191
+ `<div style="position:absolute;pointer-events:none;z-index:-1;bottom:` +
1192
+ `${bottomString};left:${leftString};right:${rightString};top:${topString}"></div>`
1193
+ );
1194
}
1195
const nextChildren = toArray(
1196
((nextChild: any): ReactElement).props.children,
packages/react-dom/src/shared/DOMProperty.js
+1
@@ -219,6 +219,7 @@ const properties = {};
219
'suppressContentEditableWarning',
220
'suppressHydrationWarning',
221
'style',
222
+ 'hydrateTouchHitTarget',
223
].forEach(name => {
224
properties[name] = new PropertyInfoRecord(
225
name,
packages/react-events/src/__tests__/TouchHitTarget-test.internal.js
+53
-7
@@ -512,9 +512,46 @@ describe('TouchHitTarget', () => {
512
ReactDOM.render(<Test />, container);
513
expect(Scheduler).toFlushWithoutYielding();
514
expect(container.innerHTML).toBe(
515
- '<div style="position: relative; z-index: 0;"><span>Random span 1</span>' +
516
- '<div style="position: absolute; z-index: -1; bottom: -10px; ' +
517
- 'left: 0px; right: -10px; top: -10px;"></div><span>Random span 2</span></div>',
515
+ '<div style="position: relative; z-index: 0;"><span>Random span 1</span><div style="position: absolute; ' +
516
+ 'z-index: -1; bottom: -10px; left: 0px; right: -10px; top: -10px;">' +
517
+ '</div><span>Random span 2</span></div>',
518
+ );
519
+ });
520
+
521
+ it('should hydrate TouchHitTarget hit slop elements correcty', () => {
522
+ const Test = () => (
523
+ <EventComponent>
524
+ <div style={{position: 'relative', zIndex: 0}}>
525
+ <TouchHitTarget />
526
+ </div>
527
+ </EventComponent>
528
+ );
529
+
530
+ const container = document.createElement('div');
531
+ container.innerHTML = '<div style="position:relative;z-index:0"></div>';
532
+ ReactDOM.hydrate(<Test />, container);
533
+ expect(Scheduler).toFlushWithoutYielding();
534
+ expect(container.innerHTML).toBe(
535
+ '<div style="position:relative;z-index:0"></div>',
536
+ );
537
+
538
+ const Test2 = () => (
539
+ <EventComponent>
540
+ <div style={{position: 'relative', zIndex: 0}}>
541
+ <TouchHitTarget top={10} left={10} right={10} bottom={10} />
542
+ </div>
543
+ </EventComponent>
544
+ );
545
+
546
+ const container2 = document.createElement('div');
547
+ container2.innerHTML =
548
+ '<div style="position:relative;z-index:0"><div style="position:absolute;pointer-events:none;z-index:-1;' +
549
+ 'bottom:-10px;left:-10px;right:-10px;top:-10px"></div></div>';
550
+ ReactDOM.hydrate(<Test2 />, container2);
551
+ expect(Scheduler).toFlushWithoutYielding();
552
+ expect(container2.innerHTML).toBe(
553
+ '<div style="position:relative;z-index:0"><div style="position: absolute; z-index: -1; ' +
554
+ 'bottom: -10px; left: -10px; right: -10px; top: -10px;"></div></div>',
555
);
556
});
557
@@ -565,7 +602,7 @@ describe('TouchHitTarget', () => {
602
expect(output).toBe('<div></div>');
603
});
604
568
- it('should render a TouchHitTarget without hit slop values', () => {
605
+ it('should render a TouchHitTarget with hit slop values', () => {
606
const Test = () => (
607
<EventComponent>
608
<div>
@@ -575,7 +612,10 @@ describe('TouchHitTarget', () => {
612
);
613
614
let output = ReactDOMServer.renderToString(<Test />);
578
- expect(output).toBe('<div></div>');
615
+ expect(output).toBe(
616
+ '<div><div style="position:absolute;pointer-events:none;z-index:-1;' +
617
+ 'bottom:-10px;left:-10px;right:-10px;top:-10px"></div></div>',
618
+ );
619
620
const Test2 = () => (
621
<EventComponent>
@@ -586,7 +626,10 @@ describe('TouchHitTarget', () => {
626
);
627
628
output = ReactDOMServer.renderToString(<Test2 />);
589
- expect(output).toBe('<div></div>');
629
+ expect(output).toBe(
630
+ '<div><div style="position:absolute;pointer-events:none;z-index:-1;' +
631
+ 'bottom:-10px;left:0px;right:0x;top:0px"></div></div>',
632
+ );
633
634
const Test3 = () => (
635
<EventComponent>
@@ -597,7 +640,10 @@ describe('TouchHitTarget', () => {
640
);
641
642
output = ReactDOMServer.renderToString(<Test3 />);
600
- expect(output).toBe('<div></div>');
643
+ expect(output).toBe(
644
+ '<div><div style="position:absolute;pointer-events:none;z-index:-1;' +
645
+ 'bottom:-4px;left:-2px;right:-3px;top:-1px"></div></div>',
646
+ );
647
});
648
});
649
});