Skip to content

Commit

Permalink
[Slide] Fix resize issue (mui#8672)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari authored and the-noob committed Oct 17, 2017
1 parent 7a3dd85 commit 3a47948
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 39 deletions.
7 changes: 6 additions & 1 deletion docs/src/pages/demos/dialogs/AlertDialogSlide.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ export default class AlertDialogSlide extends React.Component {
return (
<div>
<Button onClick={this.handleClickOpen}>Slide in alert dialog</Button>
<Dialog open={this.state.open} transition={Slide} onRequestClose={this.handleRequestClose}>
<Dialog
open={this.state.open}
transition={<Slide direction="up" />}
keepMounted
onRequestClose={this.handleRequestClose}
>
<DialogTitle>{"Use Google's location service?"}</DialogTitle>
<DialogContent>
<DialogContentText>
Expand Down
96 changes: 71 additions & 25 deletions src/transitions/Slide.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = {
Expand Down Expand Up @@ -99,25 +133,37 @@ class Slide extends React.Component<ProvidedProps & Props> {
// 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);
Expand Down Expand Up @@ -155,9 +201,7 @@ class Slide extends React.Component<ProvidedProps & Props> {
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);
Expand All @@ -176,19 +220,21 @@ class Slide extends React.Component<ProvidedProps & Props> {
} = this.props;

return (
<Transition
onEnter={this.handleEnter}
onEntering={this.handleEntering}
onExit={this.handleExit}
timeout={transitionDuration}
transitionAppear
ref={node => {
this.transition = node;
}}
{...other}
>
{children}
</Transition>
<EventListener target="window" onResize={this.handleResize}>
<Transition
onEnter={this.handleEnter}
onEntering={this.handleEntering}
onExit={this.handleExit}
timeout={transitionDuration}
transitionAppear
ref={node => {
this.transition = node;
}}
{...other}
>
{children}
</Transition>
</EventListener>
);
}
}
Expand Down
83 changes: 70 additions & 13 deletions src/transitions/Slide.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<Slide />', () => {
let shallow;
let mount;

before(() => {
shallow = createShallow({ dive: true });
mount = createMount();
});

after(() => {
mount.cleanUp();
});

it('should render a Transition', () => {
const wrapper = shallow(<Slide />);
assert.strictEqual(wrapper.name(), 'Transition');
assert.strictEqual(wrapper.name(), 'EventListener');
assert.strictEqual(wrapper.childAt(0).name(), 'Transition');
});

describe('event callbacks', () => {
Expand All @@ -30,11 +37,15 @@ describe('<Slide />', () => {
return result;
}, {});

const wrapper = shallow(<Slide {...handlers} />);
const wrapper = shallow(<Slide {...handlers} />).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`);
});
});
Expand All @@ -57,7 +68,7 @@ describe('<Slide />', () => {
/>,
);
instance = wrapper.instance();
element = { getBoundingClientRect: () => ({}), style: {} };
element = { fakeTransform: 'none', getBoundingClientRect: () => ({}), style: {} };
});

it('should create proper easeOut animation onEntering', () => {
Expand Down Expand Up @@ -93,6 +104,7 @@ describe('<Slide />', () => {

beforeEach(() => {
element = {
fakeTransform: 'none',
getBoundingClientRect: () => ({
width: 500,
height: 300,
Expand Down Expand Up @@ -146,6 +158,7 @@ describe('<Slide />', () => {

before(() => {
element = {
fakeTransform: 'none',
getBoundingClientRect: () => ({
width: 500,
height: 300,
Expand Down Expand Up @@ -176,26 +189,70 @@ describe('<Slide />', () => {
});

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
<Slide.Naked theme={createMuiTheme()} in={false}>
<div>Foo</div>
</Slide.Naked>,
);
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
<Slide.Naked theme={createMuiTheme()} in={false}>
<Slide.Naked theme={createMuiTheme()} direction="up" in={false}>
<div>Foo</div>
</Slide.Naked>,
);
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(<Slide in />);
const instance = wrapper.instance();
instance.handleResize();
clock.tick(166);
});
});
});

0 comments on commit 3a47948

Please sign in to comment.