preact8-css-transition-group
Version:
Apply CSS transitions when adding or removing Preact components/elements.
683 lines (563 loc) • 17.9 kB
JavaScript
'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