react-fader
Version:
component that fades out old children, then fades in new children when its children change
247 lines • 8.16 kB
JavaScript
/* eslint-env browser */
import * as React from 'react';
import { TransitionContext } from 'react-transition-context';
import Prefixer from 'inline-style-prefixer';
const defaultProps = {
fadeInTransitionDuration: 200,
fadeInTransitionTimingFunction: 'linear',
fadeOutTransitionDuration: 200,
fadeOutTransitionTimingFunction: 'linear',
sizeTransitionDuration: 200,
sizeTransitionTimingFunction: 'ease',
prefixer: new Prefixer(),
style: {},
measureHeight: el => el.clientHeight,
measureWidth: el => el.clientWidth,
shouldTransition(oldChildren, newChildren) {
if (oldChildren === newChildren) return false;
if ( /*#__PURE__*/React.isValidElement(oldChildren) && /*#__PURE__*/React.isValidElement(newChildren) && oldChildren.key != null && oldChildren.key === newChildren.key) {
return false;
}
return true;
}
};
function applyDefaults(props) {
const result = {
...props
};
for (const key in defaultProps) {
if (Object.prototype.hasOwnProperty.call(defaultProps, key) && props[key] == null) {
result[key] = defaultProps[key];
}
}
return result;
}
export default class Fader extends React.Component {
lastProps = this.props;
lastDefaultedProps;
getDefaultedProps = () => {
if (this.lastProps !== this.props || !this.lastDefaultedProps) {
this.lastProps = this.props;
this.lastDefaultedProps = applyDefaults(this.props);
}
return this.lastDefaultedProps;
};
wrapChildren = (children, transitionState) => {
const {
animateWidth,
prefixer,
viewStyle,
innerViewWrapperStyle,
fadeInTransitionDuration,
fadeInTransitionTimingFunction,
fadeOutTransitionDuration,
fadeOutTransitionTimingFunction
} = this.getDefaultedProps();
const style = {
display: animateWidth ? 'inline-flex' : 'flex',
transitionProperty: 'opacity',
...viewStyle
};
switch (transitionState) {
case 'out':
case 'entering':
style.opacity = transitionState === 'entering' ? 1 : 0;
style.transitionDuration = fadeInTransitionDuration + 'ms';
style.transitionTimingFunction = fadeInTransitionTimingFunction;
break;
case 'in':
case 'leaving':
style.opacity = transitionState === 'in' ? 1 : 0;
style.transitionDuration = fadeOutTransitionDuration + 'ms';
style.transitionTimingFunction = fadeOutTransitionTimingFunction;
break;
}
return /*#__PURE__*/React.createElement("div", {
"data-transition-state": transitionState,
style: prefixer.prefix(style)
}, /*#__PURE__*/React.createElement("div", {
style: prefixer.prefix({
width: animateWidth ? undefined : '100%',
...innerViewWrapperStyle
}),
ref: c => this.wrappedChildrenRef = c
}, /*#__PURE__*/React.createElement(TransitionContext, {
state: transitionState
}, children)));
};
wrappedChildrenRef;
timeouts = {};
state = {
children: this.props.children,
height: undefined,
width: undefined,
wrappedChildren: this.wrapChildren(this.props.children, 'in'),
transitionState: 'in',
transitioningSize: false
};
setTimeout(name, callback, delay) {
if (this.timeouts[name]) clearTimeout(this.timeouts[name]);
this.timeouts[name] = setTimeout(callback, delay);
}
componentDidUpdate() {
const {
transitionState,
height,
width,
transitioningSize
} = this.state;
const {
animateHeight,
animateWidth,
measureHeight,
measureWidth,
shouldTransition: _shouldTransition,
fadeOutTransitionDuration,
fadeInTransitionDuration,
sizeTransitionDuration
} = this.getDefaultedProps();
const shouldTransition = _shouldTransition(this.state.children, this.props.children);
if (transitionState === 'in' && shouldTransition) {
const newState = {};
newState.children = this.props.children;
newState.transitionState = 'leaving';
newState.wrappedChildren = this.wrapChildren(this.state.children, 'leaving');
this.setTimeout('fadeOut', this.onTransitionEnd, fadeOutTransitionDuration);
if (animateHeight && height === undefined && this.wrappedChildrenRef) {
newState.height = measureHeight(this.wrappedChildrenRef);
}
if (animateWidth && width === undefined && this.wrappedChildrenRef) {
newState.width = measureWidth(this.wrappedChildrenRef);
}
this.setState(state => ({
...state,
...newState
}));
} else if (transitionState === 'leaving' && (animateHeight || animateWidth)) {
if (!transitioningSize) this.setState({
transitioningSize: true
});
} else if (transitionState === 'out') {
const newState = {};
if (shouldTransition) {
newState.children = this.props.children;
newState.wrappedChildren = this.wrapChildren(this.props.children, 'out');
} else {
newState.transitionState = 'entering';
newState.children = this.props.children;
newState.wrappedChildren = this.wrapChildren(this.props.children, 'entering');
this.setTimeout('fadeIn', this.onTransitionEnd, fadeInTransitionDuration);
if (animateHeight) {
if (this.wrappedChildrenRef) {
newState.height = measureHeight(this.wrappedChildrenRef);
}
this.setTimeout('height', this.onSizeTransitionEnd, sizeTransitionDuration);
}
if (animateWidth) {
if (this.wrappedChildrenRef) {
newState.width = measureWidth(this.wrappedChildrenRef);
}
this.setTimeout('width', this.onSizeTransitionEnd, sizeTransitionDuration);
}
}
this.setState(state => ({
...state,
...newState
}));
} else if (!shouldTransition && this.state.children !== this.props.children) {
const newState = {};
newState.children = this.props.children;
newState.wrappedChildren = this.wrapChildren(this.props.children, transitionState);
this.setState(state => ({
...state,
...newState
}));
}
}
onTransitionEnd = () => {
const {
shouldTransition,
fadeOutTransitionDuration
} = this.getDefaultedProps();
const {
transitionState
} = this.state;
if (transitionState === 'leaving') {
this.setState({
transitionState: 'out',
wrappedChildren: this.wrapChildren(this.props.children, 'out')
});
} else if (transitionState === 'entering') {
if (shouldTransition(this.state.children, this.props.children)) {
this.setState({
transitionState: 'leaving',
wrappedChildren: this.wrapChildren(this.state.children, 'leaving')
});
this.setTimeout('fadeOut', this.onTransitionEnd, fadeOutTransitionDuration);
} else {
this.setState({
transitionState: 'in',
height: undefined,
width: undefined,
wrappedChildren: this.wrapChildren(this.props.children, 'in')
});
}
}
};
onSizeTransitionEnd = () => {
this.setState({
transitioningSize: false
});
};
componentWillUnmount() {
for (const name in this.timeouts) clearTimeout(this.timeouts[name]);
}
render() {
const {
height,
width,
transitioningSize,
wrappedChildren
} = this.state;
const {
animateWidth,
className,
prefixer,
innerRef,
style: _style,
sizeTransitionDuration,
sizeTransitionTimingFunction
} = this.getDefaultedProps();
const style = {
height,
width,
display: animateWidth ? 'inline-block' : 'block',
..._style
};
if (transitioningSize) {
style.overflow = 'hidden';
style.transition = `height ${sizeTransitionDuration}ms ${sizeTransitionTimingFunction}, width ${sizeTransitionDuration}ms ${sizeTransitionTimingFunction}`;
}
return /*#__PURE__*/React.createElement("div", {
className: className,
style: prefixer.prefix(style),
ref: innerRef
}, wrappedChildren);
}
}