UNPKG

react-fader

Version:

component that fades out old children, then fades in new children when its children change

247 lines 8.16 kB
/* 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); } }