diff --git a/packages/react-dom-bindings/src/__tests__/ReactFiberConfigDOMRTL-test.js b/packages/react-dom-bindings/src/__tests__/ReactFiberConfigDOMRTL-test.js new file mode 100644 index 00000000000..d7c38d493d1 --- /dev/null +++ b/packages/react-dom-bindings/src/__tests__/ReactFiberConfigDOMRTL-test.js @@ -0,0 +1,40 @@ +'use strict'; + +let ReactFiberConfigDOM; + +beforeEach(() => { + jest.resetModules(); + ReactFiberConfigDOM = require('../client/ReactFiberConfigDOM'); +}); + +test('measureClonedInstance adjusts x using RTL sign', () => { + const instance = document.createElement('div'); + // Simulate the element being moved out of viewport by +20000 in RTL + document.documentElement.dir = 'rtl'; + instance.getBoundingClientRect = () => new DOMRect(20100, 150, 50, 20); + const measurement = ReactFiberConfigDOM.measureClonedInstance(instance); + // Expect the x to be adjusted back to original (20100 - 20000) + expect(measurement.rect.x).toBe(100); + expect(measurement.rect.y).toBe(150 + 20000); + + // LTR case + document.documentElement.dir = 'ltr'; + instance.getBoundingClientRect = () => new DOMRect(-19900, 250, 30, 10); + const measurement2 = ReactFiberConfigDOM.measureClonedInstance(instance); + expect(measurement2.rect.x).toBe(100); + expect(measurement2.rect.y).toBe(250 + 20000); +}); + +test('moveOldFrameIntoViewport uses RTL-aware horizontal offset', () => { + const keyframe = {transform: 'none'}; + const targetElement = document.createElement('div'); + + document.documentElement.dir = 'ltr'; + ReactFiberConfigDOM.moveOldFrameIntoViewport(keyframe, targetElement); + expect(keyframe.transform.startsWith('translate(20000px, 20000px)')).toBe(true); + + keyframe.transform = 'none'; + document.documentElement.dir = 'rtl'; + ReactFiberConfigDOM.moveOldFrameIntoViewport(keyframe, targetElement); + expect(keyframe.transform.startsWith('translate(-20000px, 20000px)')).toBe(true); +}); diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 66e46961d81..87611dec86a 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -1722,18 +1722,26 @@ function moveOutOfViewport( // other transforms. That's why we need to merge the long form properties. // TODO: Ideally we'd adjust for the parent's rotate/scale. Otherwise when // we move back the ::view-transition-group we might overshoot or undershoot. - element.style.transform = 'translate(-20000px, -20000px) ' + transform; + const ownerDoc = element.ownerDocument; + const isRTL = ownerDoc && ownerDoc.documentElement && ownerDoc.documentElement.dir === 'rtl'; + const translateX = isRTL ? '20000px' : '-20000px'; + element.style.transform = 'translate(' + translateX + ', -20000px) ' + transform; } -function moveOldFrameIntoViewport(keyframe: any): void { +export function moveOldFrameIntoViewport(keyframe: any, targetElement: ?HTMLElement): void { // In the resulting View Transition Animation, the first frame will be offset. const computedTransform: ?string = keyframe.transform; if (computedTransform != null) { let transform = computedTransform === 'none' ? '' : computedTransform; - transform = 'translate(20000px, 20000px) ' + transform; + const isRTL = + targetElement && + targetElement.ownerDocument && + targetElement.ownerDocument.documentElement.dir === 'rtl'; + const dx = isRTL ? '-20000px' : '20000px'; + transform = 'translate(' + dx + ', 20000px) ' + transform; keyframe.transform = transform; } -} +} export function cloneRootViewTransitionContainer( rootContainer: Container, @@ -1942,8 +1950,12 @@ export function measureClonedInstance(instance: Instance): InstanceMeasurement { const measuredRect = instance.getBoundingClientRect(); // Adjust the DOMRect based on the translate that put it outside the viewport. // TODO: This might not be completely correct if the parent also has a transform. + const isRTL = + instance && + instance.ownerDocument && + instance.ownerDocument.documentElement.dir === 'rtl'; const rect = new DOMRect( - measuredRect.x + 20000, + measuredRect.x + (isRTL ? -20000 : 20000), measuredRect.y + 20000, measuredRect.width, measuredRect.height, @@ -2426,14 +2438,24 @@ function animateGesture( targetElement, pseudoElement, ): any).translate; + const isRTL = + targetElement && + targetElement.ownerDocument && + targetElement.ownerDocument.documentElement.dir === 'rtl'; + const translateToAdd = isRTL ? '-20000px 20000px' : '20000px 20000px'; keyframe.translate = mergeTranslate( elementTranslate, - '20000px 20000px', + translateToAdd, ); } else { + const isRTL = + targetElement && + targetElement.ownerDocument && + targetElement.ownerDocument.documentElement.dir === 'rtl'; + const translateToAdd = isRTL ? '-20000px 20000px' : '20000px 20000px'; keyframe.translate = mergeTranslate( keyframe.translate, - '20000px 20000px', + translateToAdd, ); } } @@ -2444,7 +2466,7 @@ function animateGesture( // from the old position, we need to adjust it from the out of viewport // position. If this is going from old to new it only applies to first // keyframe. Otherwise it applies to every keyframe. - moveOldFrameIntoViewport(keyframes[0]); + moveOldFrameIntoViewport(keyframes[0], targetElement); } if (unchangedDimensions && width !== undefined && height !== undefined) { // Read the underlying width/height of the pseudo-element. The previous animation