UNPKG

preact-css-transition-group

Version:

Apply CSS transitions when adding or removing Preact components/elements.

558 lines (461 loc) 15.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('preact')) : typeof define === 'function' && define.amd ? define(['preact'], factory) : (global.PreactCSSTransitionGroup = factory(global.preact)); }(this, (function (preact) { 'use strict'; function getKey(vnode) { return vnode.attributes && vnode.attributes.key; } function getComponentBase(component) { return component.base; } function onlyChild(children) { return children && children[0]; } function filterNullChildren(children) { return children && children.filter(function (i) { return i !== null; }); } function find(arr, iter) { for (var i = arr.length; i--;) { if (iter(arr[i])) return true; } return false; } function inChildrenByKey(children, key) { return find(children, function (c) { return getKey(c) === key; }); } function inChildren(children, child) { return inChildrenByKey(children, getKey(child)); } function isShownInChildrenByKey(children, key, showProp) { return find(children, function (c) { return getKey(c) === key && c.props[showProp]; }); } function isShownInChildren(children, child, showProp) { return isShownInChildrenByKey(children, getKey(child), showProp); } function mergeChildMappings(prev, next) { var ret = []; var nextChildrenPending = {}, pendingChildren = []; prev.forEach(function (c) { var key = getKey(c); if (inChildrenByKey(next, key)) { if (pendingChildren.length) { nextChildrenPending[key] = pendingChildren; pendingChildren = []; } } else { pendingChildren.push(c); } }); next.forEach(function (c) { var key = getKey(c); if (nextChildrenPending.hasOwnProperty(key)) { ret = ret.concat(nextChildrenPending[key]); } ret.push(c); }); return ret.concat(pendingChildren); } var SPACE = ' '; var RE_CLASS = /[\n\t\r]+/g; var norm = function (elemClass) { return (SPACE + elemClass + SPACE).replace(RE_CLASS, SPACE); }; function addClass(elem, className) { if (elem.classList) { var _elem$classList; (_elem$classList = elem.classList).add.apply(_elem$classList, className.split(' ')); } else { elem.className += ' ' + className; } } function removeClass(elem, needle) { needle = needle.trim(); if (elem.classList) { var _elem$classList2; (_elem$classList2 = elem.classList).remove.apply(_elem$classList2, needle.split(' ')); } else { var elemClass = elem.className.trim(); var className = norm(elemClass); needle = SPACE + needle + SPACE; while (className.indexOf(needle) >= 0) { className = className.replace(needle, SPACE); } elem.className = className.trim(); } } var EVENT_NAME_MAP = { transitionend: { transition: 'transitionend', WebkitTransition: 'webkitTransitionEnd', MozTransition: 'mozTransitionEnd', OTransition: 'oTransitionEnd', msTransition: 'MSTransitionEnd' }, animationend: { animation: 'animationend', WebkitAnimation: 'webkitAnimationEnd', MozAnimation: 'mozAnimationEnd', OAnimation: 'oAnimationEnd', msAnimation: 'MSAnimationEnd' } }; var endEvents = []; function detectEvents() { var testEl = document.createElement('div'), style = testEl.style; if (!('AnimationEvent' in window)) { delete EVENT_NAME_MAP.animationend.animation; } if (!('TransitionEvent' in window)) { delete EVENT_NAME_MAP.transitionend.transition; } for (var baseEventName in EVENT_NAME_MAP) { var baseEvents = EVENT_NAME_MAP[baseEventName]; for (var styleName in baseEvents) { if (styleName in style) { endEvents.push(baseEvents[styleName]); break; } } } } if (typeof window !== 'undefined') { detectEvents(); } function addEndEventListener(node, eventListener) { if (!endEvents.length) { return window.setTimeout(eventListener, 0); } endEvents.forEach(function (endEvent) { node.addEventListener(endEvent, eventListener, false); }); } function removeEndEventListener(node, eventListener) { if (!endEvents.length) return; endEvents.forEach(function (endEvent) { node.removeEventListener(endEvent, eventListener, false); }); } var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; var objectWithoutProperties = function (obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }; var possibleConstructorReturn = function (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }; var TICK = 17; var CSSTransitionGroupChild = function (_Component) { inherits(CSSTransitionGroupChild, _Component); function CSSTransitionGroupChild() { var _temp, _this, _ret; classCallCheck(this, CSSTransitionGroupChild); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))), _this), _this.flushClassNameQueue = function () { if (getComponentBase(_this)) { addClass(getComponentBase(_this), _this.classNameQueue.join(' ')); } _this.classNameQueue.length = 0; _this.timeout = null; }, _temp), possibleConstructorReturn(_this, _ret); } CSSTransitionGroupChild.prototype.transition = function transition(animationType, finishCallback, timeout) { var _this2 = this; var node = getComponentBase(this); var className = this.props.name[animationType] || this.props.name + '-' + animationType; var activeClassName = this.props.name[animationType + 'Active'] || className + '-active'; var timer = null; if (this.endListener) { this.endListener(); } this.endListener = function (e) { if (e && e.target !== node) return; clearTimeout(timer); removeClass(node, className); removeClass(node, activeClassName); removeEndEventListener(node, _this2.endListener); _this2.endListener = null; if (finishCallback) { finishCallback(); } }; if (timeout) { timer = setTimeout(this.endListener, timeout); this.transitionTimeouts.push(timer); } else { addEndEventListener(node, this.endListener); } addClass(node, className); this.queueClass(activeClassName); }; CSSTransitionGroupChild.prototype.queueClass = function queueClass(className) { this.classNameQueue.push(className); if (!this.timeout) { this.timeout = setTimeout(this.flushClassNameQueue, TICK); } }; CSSTransitionGroupChild.prototype.stop = function stop() { if (this.timeout) { clearTimeout(this.timeout); this.classNameQueue.length = 0; this.timeout = null; } if (this.endListener) { this.endListener(); } }; CSSTransitionGroupChild.prototype.componentWillMount = function componentWillMount() { this.classNameQueue = []; this.transitionTimeouts = []; }; CSSTransitionGroupChild.prototype.componentWillUnmount = function componentWillUnmount() { if (this.timeout) { clearTimeout(this.timeout); } this.transitionTimeouts.forEach(function (timeout) { clearTimeout(timeout); }); }; CSSTransitionGroupChild.prototype.componentWillEnter = function componentWillEnter(done) { if (this.props.enter) { this.transition('enter', done, this.props.enterTimeout); } else { done(); } }; CSSTransitionGroupChild.prototype.componentWillLeave = function componentWillLeave(done) { if (this.props.leave) { this.transition('leave', done, this.props.leaveTimeout); } else { done(); } }; CSSTransitionGroupChild.prototype.render = function render() { return onlyChild(this.props.children); }; return CSSTransitionGroupChild; }(preact.Component); var CSSTransitionGroup = function (_Component) { inherits(CSSTransitionGroup, _Component); function CSSTransitionGroup(props) { classCallCheck(this, CSSTransitionGroup); var _this = possibleConstructorReturn(this, _Component.call(this)); _this.renderChild = function (child) { var _this$props = _this.props; var transitionName = _this$props.transitionName; var transitionEnter = _this$props.transitionEnter; var transitionLeave = _this$props.transitionLeave; var transitionEnterTimeout = _this$props.transitionEnterTimeout; var transitionLeaveTimeout = _this$props.transitionLeaveTimeout; var key = getKey(child); return preact.h( CSSTransitionGroupChild, { key: key, ref: function (c) { if (!(_this.refs[key] = c)) child = null; }, name: transitionName, enter: transitionEnter, leave: transitionLeave, enterTimeout: transitionEnterTimeout, leaveTimeout: transitionLeaveTimeout }, child ); }; _this.refs = {}; _this.state = { children: (props.children || []).slice() }; return _this; } CSSTransitionGroup.prototype.shouldComponentUpdate = function shouldComponentUpdate(_, _ref) { var children = _ref.children; return children !== this.state.children; }; CSSTransitionGroup.prototype.componentWillMount = function componentWillMount() { this.currentlyTransitioningKeys = {}; this.keysToEnter = []; this.keysToLeave = []; }; CSSTransitionGroup.prototype.componentWillReceiveProps = function componentWillReceiveProps(_ref2) { var _this2 = this; var children = _ref2.children; var exclusive = _ref2.exclusive; var showProp = _ref2.showProp; var nextChildMapping = filterNullChildren(children || []).slice(); var prevChildMapping = filterNullChildren(exclusive ? this.props.children : this.state.children); var newChildren = mergeChildMappings(prevChildMapping, nextChildMapping); if (showProp) { newChildren = newChildren.map(function (c) { if (!c.props[showProp] && isShownInChildren(prevChildMapping, c, showProp)) { var _cloneElement; c = preact.cloneElement(c, (_cloneElement = {}, _cloneElement[showProp] = true, _cloneElement)); } return c; }); } if (exclusive) { newChildren.forEach(function (c) { return _this2.stop(getKey(c)); }); } this.setState({ children: newChildren }); this.forceUpdate(); nextChildMapping.forEach(function (c) { var key = c.key; var hasPrev = prevChildMapping && inChildren(prevChildMapping, c); if (showProp) { if (hasPrev) { var showInPrev = isShownInChildren(prevChildMapping, c, showProp), showInNow = c.props[showProp]; if (!showInPrev && showInNow && !_this2.currentlyTransitioningKeys[key]) { _this2.keysToEnter.push(key); } } } else if (!hasPrev && !_this2.currentlyTransitioningKeys[key]) { _this2.keysToEnter.push(key); } }); prevChildMapping.forEach(function (c) { var key = c.key; var hasNext = nextChildMapping && inChildren(nextChildMapping, c); if (showProp) { if (hasNext) { var showInNext = isShownInChildren(nextChildMapping, c, showProp); var showInNow = c.props[showProp]; if (!showInNext && showInNow && !_this2.currentlyTransitioningKeys[key]) { _this2.keysToLeave.push(key); } } } else if (!hasNext && !_this2.currentlyTransitioningKeys[key]) { _this2.keysToLeave.push(key); } }); }; CSSTransitionGroup.prototype.performEnter = function performEnter(key) { var _this3 = this; this.currentlyTransitioningKeys[key] = true; var component = this.refs[key]; if (component.componentWillEnter) { component.componentWillEnter(function () { return _this3._handleDoneEntering(key); }); } else { this._handleDoneEntering(key); } }; CSSTransitionGroup.prototype._handleDoneEntering = function _handleDoneEntering(key) { delete this.currentlyTransitioningKeys[key]; var currentChildMapping = filterNullChildren(this.props.children), showProp = this.props.showProp; if (!currentChildMapping || !showProp && !inChildrenByKey(currentChildMapping, key) || showProp && !isShownInChildrenByKey(currentChildMapping, key, showProp)) { this.performLeave(key); } else { this.setState({ children: currentChildMapping }); } }; CSSTransitionGroup.prototype.stop = function stop(key) { delete this.currentlyTransitioningKeys[key]; var component = this.refs[key]; if (component) component.stop(); }; CSSTransitionGroup.prototype.performLeave = function performLeave(key) { var _this4 = this; this.currentlyTransitioningKeys[key] = true; var component = this.refs[key]; if (component && component.componentWillLeave) { component.componentWillLeave(function () { return _this4._handleDoneLeaving(key); }); } else { this._handleDoneLeaving(key); } }; CSSTransitionGroup.prototype._handleDoneLeaving = function _handleDoneLeaving(key) { delete this.currentlyTransitioningKeys[key]; var showProp = this.props.showProp, currentChildMapping = filterNullChildren(this.props.children); if (showProp && currentChildMapping && isShownInChildrenByKey(currentChildMapping, key, showProp)) { this.performEnter(key); } else if (!showProp && currentChildMapping && inChildrenByKey(currentChildMapping, key)) { this.performEnter(key); } else { this.setState({ children: currentChildMapping }); } }; CSSTransitionGroup.prototype.componentDidUpdate = function componentDidUpdate() { var _this5 = this; var keysToEnter = this.keysToEnter; var keysToLeave = this.keysToLeave; this.keysToEnter = []; keysToEnter.forEach(function (k) { return _this5.performEnter(k); }); this.keysToLeave = []; keysToLeave.forEach(function (k) { return _this5.performLeave(k); }); }; CSSTransitionGroup.prototype.render = function render(_ref3, _ref4) { var Component = _ref3.component; var transitionName = _ref3.transitionName; var transitionEnter = _ref3.transitionEnter; var transitionLeave = _ref3.transitionLeave; var transitionEnterTimeout = _ref3.transitionEnterTimeout; var transitionLeaveTimeout = _ref3.transitionLeaveTimeout; var c = _ref3.children; var props = objectWithoutProperties(_ref3, ['component', 'transitionName', 'transitionEnter', 'transitionLeave', 'transitionEnterTimeout', 'transitionLeaveTimeout', 'children']); var children = _ref4.children; return preact.h( Component, props, filterNullChildren(children).map(this.renderChild) ); }; return CSSTransitionGroup; }(preact.Component); CSSTransitionGroup.defaultProps = { component: 'span', transitionEnter: true, transitionLeave: true }; return CSSTransitionGroup; }))); //# sourceMappingURL=preact-css-transition-group.js.map