UNPKG

preact8-css-transition-group

Version:

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

683 lines (563 loc) 17.9 kB
'use strict'; var preact = require('preact'); 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; }); } var requestAnimationFrame = function (callback) { if (typeof window !== 'undefined' && window.requestAnimationFrame) { window.requestAnimationFrame(callback); } else { setTimeout(callback, 17); } }; 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 asyncGenerator = function () { function AwaitValue(value) { this.value = value; } function AsyncGenerator(gen) { var front, back; function send(key, arg) { return new Promise(function (resolve, reject) { var request = { key: key, arg: arg, resolve: resolve, reject: reject, next: null }; if (back) { back = back.next = request; } else { front = back = request; resume(key, arg); } }); } function resume(key, arg) { try { var result = gen[key](arg); var value = result.value; if (value instanceof AwaitValue) { Promise.resolve(value.value).then(function (arg) { resume("next", arg); }, function (arg) { resume("throw", arg); }); } else { settle(result.done ? "return" : "normal", result.value); } } catch (err) { settle("throw", err); } } function settle(type, value) { switch (type) { case "return": front.resolve({ value: value, done: true }); break; case "throw": front.reject(value); break; default: front.resolve({ value: value, done: false }); break; } front = front.next; if (front) { resume(front.key, front.arg); } else { back = null; } } this._invoke = send; if (typeof gen.return !== "function") { this.return = undefined; } } if (typeof Symbol === "function" && Symbol.asyncIterator) { AsyncGenerator.prototype[Symbol.asyncIterator] = function () { return this; }; } AsyncGenerator.prototype.next = function (arg) { return this._invoke("next", arg); }; AsyncGenerator.prototype.throw = function (arg) { return this._invoke("throw", arg); }; AsyncGenerator.prototype.return = function (arg) { return this._invoke("return", arg); }; return { wrap: function (fn) { return function () { return new AsyncGenerator(fn.apply(this, arguments)); }; }, await: function (value) { return new AwaitValue(value); } }; }(); 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 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.rafHandle = null; }, _temp), possibleConstructorReturn(_this, _ret); } CSSTransitionGroupChild.prototype.transition = function transition(animationType, finishCallback, timeout) { var _this2 = this; if (!timeout) { this.raiseTimeoutConsoleError(animationType); } 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.raiseTimeoutConsoleError = function raiseTimeoutConsoleError(type) { var timeoutType = type === 'enter' ? 'transitionEnterTimeout' : 'transitionLeaveTimeout'; console.error(timeoutType + ' should be specified'); }; CSSTransitionGroupChild.prototype.queueClass = function queueClass(className) { this.classNameQueue.push(className); if (!this.rafHandle) { this.rafHandle = requestAnimationFrame(this.flushClassNameQueue); } }; CSSTransitionGroupChild.prototype.stop = function stop() { if (this.rafHandle) { this.classNameQueue.length = 0; this.rafHandle = null; } if (this.endListener) { this.endListener(); } }; CSSTransitionGroupChild.prototype.componentWillMount = function componentWillMount() { this.classNameQueue = []; this.transitionTimeouts = []; }; CSSTransitionGroupChild.prototype.componentWillUnmount = function componentWillUnmount() { this.classNameQueue.length = 0; this.rafHandle = null; 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, transitionName = _this$props.transitionName, transitionEnter = _this$props.transitionEnter, transitionLeave = _this$props.transitionLeave, transitionEnterTimeout = _this$props.transitionEnterTimeout, transitionLeaveTimeout = _this$props.transitionLeaveTimeout, 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, exclusive = _ref2.exclusive, 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, 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, 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, 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 children = _ref4.children; var Component = _ref3.component, transitionName = _ref3.transitionName, transitionEnter = _ref3.transitionEnter, transitionLeave = _ref3.transitionLeave, transitionEnterTimeout = _ref3.transitionEnterTimeout, transitionLeaveTimeout = _ref3.transitionLeaveTimeout, c = _ref3.children, props = objectWithoutProperties(_ref3, ['component', 'transitionName', 'transitionEnter', 'transitionLeave', 'transitionEnterTimeout', 'transitionLeaveTimeout', 'children']); return preact.h( Component, props, filterNullChildren(children).map(this.renderChild) ); }; return CSSTransitionGroup; }(preact.Component); CSSTransitionGroup.defaultProps = { component: 'span', transitionEnter: true, transitionLeave: true }; module.exports = CSSTransitionGroup; //# sourceMappingURL=preact-css-transition-group.js.map