From b9cf59686db4812adabb72bf1f0bd5d4ba36dfec Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 13 Oct 2017 00:17:34 +0200 Subject: [PATCH] [Slide] Fix resize issue --- .../pages/demos/dialogs/AlertDialogSlide.js | 7 +- src/transitions/Slide.js | 96 ++++++++++++++----- src/transitions/Slide.spec.js | 83 +++++++++++++--- 3 files changed, 147 insertions(+), 39 deletions(-) diff --git a/docs/src/pages/demos/dialogs/AlertDialogSlide.js b/docs/src/pages/demos/dialogs/AlertDialogSlide.js index 3dd0bb676a4a37..36ab7062501a08 100644 --- a/docs/src/pages/demos/dialogs/AlertDialogSlide.js +++ b/docs/src/pages/demos/dialogs/AlertDialogSlide.js @@ -27,7 +27,12 @@ export default class AlertDialogSlide extends React.Component { return (
- + } + keepMounted + onRequestClose={this.handleRequestClose} + > {"Use Google's location service?"} diff --git a/src/transitions/Slide.js b/src/transitions/Slide.js index 4300823cb62752..86dc57803f6826 100644 --- a/src/transitions/Slide.js +++ b/src/transitions/Slide.js @@ -3,6 +3,8 @@ import React from 'react'; import type { Element } from 'react'; import { findDOMNode } from 'react-dom'; +import EventListener from 'react-event-listener'; +import debounce from 'lodash/debounce'; import Transition from '../internal/Transition'; import withTheme from '../styles/withTheme'; import { duration } from '../styles/transitions'; @@ -17,18 +19,50 @@ function getTranslateValue(props, element: HTMLElement) { const { direction } = props; const rect = element.getBoundingClientRect(); + let transform; + + if (element.fakeTransform) { + transform = element.fakeTransform; + } else { + const computedStyle = window.getComputedStyle(element); + transform = + computedStyle.getPropertyValue('-webkit-transform') || + computedStyle.getPropertyValue('transform'); + } + + let offsetX = 0; + let offsetY = 0; + + if (transform && transform !== 'none' && typeof transform === 'string') { + const transformValues = transform + .split('(')[1] + .split(')')[0] + .split(','); + offsetX = parseInt(transformValues[4], 10); + offsetY = parseInt(transformValues[5], 10); + } + if (direction === 'left') { - return `translateX(100vw) translateX(-${rect.left}px)`; + return `translateX(100vw) translateX(-${rect.left - offsetX}px)`; } else if (direction === 'right') { return `translateX(-${rect.left + rect.width + GUTTER}px)`; } else if (direction === 'up') { - return `translateY(100vh) translateY(-${rect.top}px)`; + return `translateY(100vh) translateY(-${rect.top - offsetY}px)`; } // direction === 'down return `translate3d(0, ${0 - (rect.top + rect.height)}px, 0)`; } +export function setTranslateValue(props: Object, element: HTMLElement | Object) { + const transform = getTranslateValue(props, element); + + if (transform) { + element.style.transform = transform; + element.style.webkitTransform = transform; + } +} + export type Direction = 'left' | 'right' | 'up' | 'down'; type ProvidedProps = { @@ -99,25 +133,37 @@ class Slide extends React.Component { // otherwise component will be shown when in=false. const element = findDOMNode(this.transition); if (element instanceof HTMLElement) { - const transform = getTranslateValue(this.props, element); - element.style.transform = transform; - element.style.webkitTransform = transform; + setTranslateValue(this.props, element); } } } + componentWillUnmount() { + this.handleResize.cancel(); + } + transition = null; + handleResize = debounce(() => { + // Skip configuration where the position is screen size invariant. + if (this.props.in || this.props.direction === 'down' || this.props.direction === 'right') { + return; + } + + const element = findDOMNode(this.transition); + if (element instanceof HTMLElement) { + setTranslateValue(this.props, element); + } + }, 166); + handleEnter = element => { // Reset the transformation when needed. - // That's triggering a reflow. + // This is triggering a reflow. if (element.style.transform) { element.style.transform = 'translate3d(0, 0, 0)'; element.style.webkitTransform = 'translate3d(0, 0, 0)'; } - const transform = getTranslateValue(this.props, element); - element.style.transform = transform; - element.style.webkitTransform = transform; + setTranslateValue(this.props, element); if (this.props.onEnter) { this.props.onEnter(element); @@ -155,9 +201,7 @@ class Slide extends React.Component { typeof transitionDuration === 'number' ? transitionDuration : transitionDuration.exit, easing: theme.transitions.easing.sharp, }); - const transform = getTranslateValue(this.props, element); - element.style.transform = transform; - element.style.webkitTransform = transform; + setTranslateValue(this.props, element); if (this.props.onExit) { this.props.onExit(element); @@ -176,19 +220,21 @@ class Slide extends React.Component { } = this.props; return ( - { - this.transition = node; - }} - {...other} - > - {children} - + + { + this.transition = node; + }} + {...other} + > + {children} + + ); } } diff --git a/src/transitions/Slide.spec.js b/src/transitions/Slide.spec.js index f0d7e2409a34ca..281b5319566967 100644 --- a/src/transitions/Slide.spec.js +++ b/src/transitions/Slide.spec.js @@ -2,23 +2,30 @@ import React from 'react'; import { assert } from 'chai'; -import { spy } from 'sinon'; +import { spy, useFakeTimers } from 'sinon'; import { findDOMNode } from 'react-dom'; import { createShallow, createMount } from '../test-utils'; -import Slide from './Slide'; +import Slide, { setTranslateValue } from './Slide'; import transitions, { easing } from '../styles/transitions'; import createMuiTheme from '../styles/createMuiTheme'; describe('', () => { let shallow; + let mount; before(() => { shallow = createShallow({ dive: true }); + mount = createMount(); + }); + + after(() => { + mount.cleanUp(); }); it('should render a Transition', () => { const wrapper = shallow(); - assert.strictEqual(wrapper.name(), 'Transition'); + assert.strictEqual(wrapper.name(), 'EventListener'); + assert.strictEqual(wrapper.childAt(0).name(), 'Transition'); }); describe('event callbacks', () => { @@ -30,11 +37,15 @@ describe('', () => { return result; }, {}); - const wrapper = shallow(); + const wrapper = shallow().childAt(0); events.forEach(n => { const event = n.charAt(2).toLowerCase() + n.slice(3); - wrapper.simulate(event, { style: {}, getBoundingClientRect: () => ({}) }); + wrapper.simulate(event, { + fakeTransform: 'none', + style: {}, + getBoundingClientRect: () => ({}), + }); assert.strictEqual(handlers[n].callCount, 1, `should have called the ${n} handler`); }); }); @@ -57,7 +68,7 @@ describe('', () => { />, ); instance = wrapper.instance(); - element = { getBoundingClientRect: () => ({}), style: {} }; + element = { fakeTransform: 'none', getBoundingClientRect: () => ({}), style: {} }; }); it('should create proper easeOut animation onEntering', () => { @@ -93,6 +104,7 @@ describe('', () => { beforeEach(() => { element = { + fakeTransform: 'none', getBoundingClientRect: () => ({ width: 500, height: 300, @@ -146,6 +158,7 @@ describe('', () => { before(() => { element = { + fakeTransform: 'none', getBoundingClientRect: () => ({ width: 500, height: 300, @@ -176,26 +189,70 @@ describe('', () => { }); describe('mount', () => { - let mount; + it('should work when initially hidden', () => { + const wrapper = mount( + // $FlowFixMe - HOC is hoisting of static Naked, not sure how to represent that + +
Foo
+
, + ); + const transition = findDOMNode(wrapper.instance().transition); + // $FlowFixMe + assert.notStrictEqual(transition.style.transform, undefined); + }); + }); + + describe('resize', () => { + let clock; before(() => { - mount = createMount(); + clock = useFakeTimers(); }); after(() => { - mount.cleanUp(); + clock.restore(); }); - it('should work when initially hidden', () => { + it('should recompute the correct position', () => { const wrapper = mount( // $FlowFixMe - HOC is hoisting of static Naked, not sure how to represent that - +
Foo
, ); - const transition = findDOMNode(wrapper.instance().transition); + const instance = wrapper.instance(); + instance.handleResize(); + clock.tick(166); + const transition = findDOMNode(instance.transition); // $FlowFixMe - assert.notStrictEqual(transition ? transition.style.transform : undefined, undefined); + assert.notStrictEqual(transition.style.transform, undefined); + }); + + it('should take existing transform into account', () => { + const props = { + direction: 'up', + }; + const element = { + fakeTransform: 'transform matrix(1, 0, 0, 1, 0, 420)', + getBoundingClientRect: () => ({ + width: 500, + height: 300, + left: 300, + right: 800, + top: 1200, + bottom: 1500, + }), + style: {}, + }; + setTranslateValue(props, element); + assert.strictEqual(element.style.transform, 'translateY(100vh) translateY(-780px)'); + }); + + it('should do nothing when visible', () => { + const wrapper = shallow(); + const instance = wrapper.instance(); + instance.handleResize(); + clock.tick(166); }); }); });