zangai-react
Version:
293 lines (292 loc) • 13.2 kB
JavaScript
import * as tslib_1 from "tslib";
import * as React from 'react';
import { buildClassName as cx } from './core/css-builder';
var ANIMATION_STATE_CLASSES = {
animating: 'rah-animating',
animatingUp: 'rah-animating--up',
animatingDown: 'rah-animating--down',
animatingToHeightZero: 'rah-animating--to-height-zero',
animatingToHeightAuto: 'rah-animating--to-height-auto',
animatingToHeightSpecific: 'rah-animating--to-height-specific',
static: 'rah-static',
staticHeightZero: 'rah-static--height-zero',
staticHeightAuto: 'rah-static--height-auto',
staticHeightSpecific: 'rah-static--height-specific',
};
var PROPS_TO_OMIT = [
'animateOpacity',
'animationStateClasses',
'applyInlineTransitions',
'children',
'contentClassName',
'delay',
'duration',
'easing',
'height',
'onAnimationEnd',
'onAnimationStart',
];
function omit(obj) {
var keys = [];
for (var _i = 1; _i < arguments.length; _i++) {
keys[_i - 1] = arguments[_i];
}
if (!keys.length) {
return obj;
}
var res = {};
var objectKeys = Object.keys(obj);
for (var i = 0; i < objectKeys.length; i++) {
var key = objectKeys[i];
if (keys.indexOf(key) === -1) {
res[key] = obj[key];
}
}
return res;
}
// Start animation helper using nested requestAnimationFrames
function startAnimationHelper(callback) {
requestAnimationFrame(function () {
requestAnimationFrame(function () {
callback();
});
});
}
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function isPercentage(height) {
// Percentage height
return typeof height === 'string' &&
height.search('%') === height.length - 1 &&
isNumber(height.substr(0, height.length - 1));
}
function runCallback(callback, params) {
if (callback && typeof callback === 'function') {
callback(params);
}
}
var AtAnimationHeight = /** @class */ (function (_super) {
tslib_1.__extends(AtAnimationHeight, _super);
function AtAnimationHeight(props) {
var _this = _super.call(this, props) || this;
var height = 'auto';
var overflow = 'visible';
if (isNumber(props.height)) {
height = props.height < 0 ? 0 : props.height;
overflow = 'hidden';
}
else if (isPercentage(props.height)) {
height = props.height;
overflow = 'hidden';
}
_this.animationStateClasses = tslib_1.__assign({}, ANIMATION_STATE_CLASSES, props.animationStateClasses);
var animationStateClasses = _this.getStaticStateClasses(height);
_this.state = {
animationStateClasses: animationStateClasses,
height: height,
overflow: overflow,
shouldUseTransitions: false,
};
return _this;
}
AtAnimationHeight.prototype.componentDidMount = function () {
var height = this.state.height;
// Hide content if height is 0 (to prevent tabbing into it)
// Check for contentElement is added cause this would fail in tests (react-test-renderer)
// Read more here: https://github.com/Stanko/react-animate-height/issues/17
if (this.contentElement && this.contentElement.style) {
this.hideContent(height);
}
};
AtAnimationHeight.prototype.componentDidUpdate = function (prevProps, prevState) {
var _this = this;
var _a;
var _b = this.props, delay = _b.delay, duration = _b.duration, height = _b.height, onAnimationEnd = _b.onAnimationEnd, onAnimationStart = _b.onAnimationStart;
// Check if 'height' prop has changed
if (this.contentElement && height !== prevProps.height) {
// Remove display: none from the content div
// if it was hidden to prevent tabbing into it
this.showContent(prevState.height);
// Cache content height
this.contentElement.style.overflow = 'hidden';
var contentHeight = this.contentElement.offsetHeight;
this.contentElement.style.overflow = '';
// set total animation time
var totalDuration = duration + delay;
var newHeight_1 = null;
var timeoutState_1 = {
height: null,
overflow: 'hidden',
};
var isCurrentHeightAuto = prevState.height === 'auto';
if (isNumber(height)) {
// If new height is a number
newHeight_1 = height < 0 ? 0 : height;
timeoutState_1.height = newHeight_1;
}
else if (isPercentage(height)) {
newHeight_1 = height;
timeoutState_1.height = newHeight_1;
}
else {
// If not, animate to content height
// and then reset to auto
newHeight_1 = contentHeight; // TODO solve contentHeight = 0
timeoutState_1.height = 'auto';
timeoutState_1.overflow = null;
}
if (isCurrentHeightAuto) {
// This is the height to be animated to
timeoutState_1.height = newHeight_1;
// If previous height was 'auto'
// set starting height explicitly to be able to use transition
newHeight_1 = contentHeight;
}
// Animation classes
var animationStateClasses = cx((_a = {},
_a[this.animationStateClasses.animating] = true,
_a[this.animationStateClasses.animatingUp] = prevProps.height === 'auto' || height < prevProps.height,
_a[this.animationStateClasses.animatingDown] = height === 'auto' || height > prevProps.height,
_a[this.animationStateClasses.animatingToHeightZero] = timeoutState_1.height === 0,
_a[this.animationStateClasses.animatingToHeightAuto] = timeoutState_1.height === 'auto',
_a[this.animationStateClasses.animatingToHeightSpecific] = timeoutState_1.height > 0,
_a));
// Animation classes to be put after animation is complete
var timeoutAnimationStateClasses_1 = this.getStaticStateClasses(timeoutState_1.height);
// Set starting height and animating classes
// We are safe to call set state as it will not trigger infinite loop
// because of the "height !== prevProps.height" check
this.setState({
animationStateClasses: animationStateClasses,
height: newHeight_1,
overflow: 'hidden',
// When animating from 'auto' we first need to set fixed height
// that change should be animated
shouldUseTransitions: !isCurrentHeightAuto,
});
// Clear timeouts
clearTimeout(this.timeoutID);
clearTimeout(this.animationClassesTimeoutID);
if (isCurrentHeightAuto) {
// When animating from 'auto' we use a short timeout to start animation
// after setting fixed height above
timeoutState_1.shouldUseTransitions = true;
startAnimationHelper(function () {
_this.setState(timeoutState_1);
// ANIMATION STARTS, run a callback if it exists
if (onAnimationStart)
runCallback(onAnimationStart, { newHeight: timeoutState_1.height });
});
// Set static classes and remove transitions when animation ends
this.animationClassesTimeoutID = setTimeout(function () {
_this.setState({
animationStateClasses: timeoutAnimationStateClasses_1,
shouldUseTransitions: false,
});
// ANIMATION ENDS
// Hide content if height is 0 (to prevent tabbing into it)
_this.hideContent(timeoutState_1.height);
// Run a callback if it exists
if (onAnimationEnd)
runCallback(onAnimationEnd, { newHeight: timeoutState_1.height });
}, totalDuration);
}
else {
// ANIMATION STARTS, run a callback if it exists
if (onAnimationStart)
runCallback(onAnimationStart, { newHeight: newHeight_1 });
// Set end height, classes and remove transitions when animation is complete
this.timeoutID = setTimeout(function () {
timeoutState_1.animationStateClasses = timeoutAnimationStateClasses_1;
timeoutState_1.shouldUseTransitions = false;
_this.setState(timeoutState_1);
// ANIMATION ENDS
// If height is auto, don't hide the content
// (case when element is empty, therefore height is 0)
if (height !== 'auto') {
// Hide content if height is 0 (to prevent tabbing into it)
_this.hideContent(newHeight_1); // TODO solve newHeight = 0
}
// Run a callback if it exists
if (onAnimationEnd)
runCallback(onAnimationEnd, { newHeight: newHeight_1 });
}, totalDuration);
}
}
};
AtAnimationHeight.prototype.componentWillUnmount = function () {
clearTimeout(this.timeoutID);
clearTimeout(this.animationClassesTimeoutID);
this.timeoutID = null;
this.animationClassesTimeoutID = null;
this.animationStateClasses = null;
};
AtAnimationHeight.prototype.showContent = function (height) {
if (height === 0) {
this.contentElement.style.display = '';
}
};
AtAnimationHeight.prototype.hideContent = function (newHeight) {
if (newHeight === 0) {
this.contentElement.style.display = 'none';
}
};
AtAnimationHeight.prototype.getStaticStateClasses = function (height) {
var _a;
return cx((_a = {},
_a[this.animationStateClasses.static] = true,
_a[this.animationStateClasses.staticHeightZero] = height === 0,
_a[this.animationStateClasses.staticHeightSpecific] = height > 0,
_a[this.animationStateClasses.staticHeightAuto] = height === 'auto',
_a));
};
AtAnimationHeight.prototype.render = function () {
var _this = this;
var _a;
var _b = this.props, animateOpacity = _b.animateOpacity, applyInlineTransitions = _b.applyInlineTransitions, children = _b.children, className = _b.className, contentClassName = _b.contentClassName, duration = _b.duration, easing = _b.easing, delay = _b.delay, style = _b.style;
var _c = this.state, height = _c.height, overflow = _c.overflow, animationStateClasses = _c.animationStateClasses, shouldUseTransitions = _c.shouldUseTransitions;
var componentStyle = tslib_1.__assign({}, style, { height: height, overflow: overflow || style.overflow });
if (shouldUseTransitions && applyInlineTransitions) {
componentStyle.transition = "height " + duration + "ms " + easing + " " + delay + "ms";
// Include transition passed through styles
if (style.transition) {
componentStyle.transition = style.transition + ", " + componentStyle.transition;
}
// Add webkit vendor prefix still used by opera, blackberry...
componentStyle.WebkitTransition = componentStyle.transition;
}
var contentStyle = {};
if (animateOpacity) {
contentStyle.transition = "opacity " + duration + "ms " + easing + " " + delay + "ms";
// Add webkit vendor prefix still used by opera, blackberry...
contentStyle.WebkitTransition = contentStyle.transition;
if (height === 0) {
contentStyle.opacity = 0;
}
}
var componentClasses = cx((_a = {},
_a[animationStateClasses] = true,
_a[className] = className,
_a));
return (React.createElement("div", tslib_1.__assign({}, omit.apply(void 0, [this.props].concat(PROPS_TO_OMIT)), { "aria-hidden": height === 0, className: componentClasses, style: componentStyle }),
React.createElement("div", { className: contentClassName, style: contentStyle, ref: function (el) { return _this.contentElement = el; } }, children)));
};
AtAnimationHeight.defaultProps = {
animateOpacity: false,
animationStateClasses: ANIMATION_STATE_CLASSES,
applyInlineTransitions: true,
duration: 250,
delay: 0,
easing: 'ease',
style: {},
onAnimationEnd: function () {
},
onAnimationStart: function () {
},
};
return AtAnimationHeight;
}(React.Component));
export { AtAnimationHeight };
;
export default AtAnimationHeight;