@samitouri / QOS-React-2 / commits / d38cfd452f

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 });