UNPKG

@spotware/react-collapse

Version:

Component-wrapper for collapse animation with react-motion for elements with variable (and dynamic) height

290 lines (237 loc) 10.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Collapse = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactMotion = require('react-motion'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(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; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(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; } function _inherits(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; } /* eslint-disable react/no-did-update-set-state,react/no-did-mount-set-state */ var SPRING_PRECISION = 1; var WAITING = 'WAITING'; var RESIZING = 'RESIZING'; var RESTING = 'RESTING'; var IDLING = 'IDLING'; var noop = function noop() { return null; }; var css = { collapse: 'ReactCollapse--collapse', content: 'ReactCollapse--content' }; function getContentHeightDefault(content, _wrapper) { return content.clientHeight; } var Collapse = exports.Collapse = function (_React$PureComponent) { _inherits(Collapse, _React$PureComponent); function Collapse(props) { _classCallCheck(this, Collapse); var _this = _possibleConstructorReturn(this, (Collapse.__proto__ || Object.getPrototypeOf(Collapse)).call(this, props)); _initialiseProps.call(_this); _this.state = { currentState: IDLING, from: 0, to: 0, resizingFrom: 0 }; return _this; } _createClass(Collapse, [{ key: 'componentDidMount', value: function componentDidMount() { var _props = this.props, isOpened = _props.isOpened, forceInitialAnimation = _props.forceInitialAnimation, onRest = _props.onRest; if (isOpened) { var to = this.getTo(); if (forceInitialAnimation) { var from = this.wrapper.clientHeight; this.setState({ currentState: RESIZING, from: from, to: to }); } else { this.setState({ currentState: IDLING, from: to, to: to }); } } onRest(); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { var _state = this.state, currentState = _state.currentState, to = _state.to, resizingFrom = _state.resizingFrom; var _props2 = this.props, isOpened = _props2.isOpened, animateContentChanges = _props2.animateContentChanges; var openedChanged = nextProps.isOpened !== isOpened; if (currentState === RESIZING && openedChanged) { this.setState({ from: this.currentHeight, to: resizingFrom, resizingFrom: to }); } else if (nextProps.hasNestedCollapse) { // For nested collapses we do not need to change to waiting state // and should keep `height:auto` // Because children will be animated and height will not jump anyway // See https://github.com/nkbt/react-collapse/issues/76 for more details if (openedChanged) { // Still go to WAITING state if own isOpened was changed this.setState({ currentState: WAITING }); } } else if (currentState === IDLING) { if (openedChanged || isOpened && animateContentChanges) { this.setState({ currentState: WAITING }); } } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps, prevState) { var _props3 = this.props, isOpened = _props3.isOpened, onRest = _props3.onRest, onMeasure = _props3.onMeasure; var currentState = this.state.currentState; if (currentState === IDLING) { onRest(); return; } if (prevState.to !== this.state.to) { onMeasure({ height: this.state.to, width: this.content.clientWidth }); } if (currentState !== RESIZING) { var from = this.wrapper.clientHeight; var to = isOpened ? this.getTo() : 0; if (from !== to) { this.setState({ currentState: RESIZING, from: from, to: to, resizingFrom: from }); return; } if (currentState === RESTING || currentState === WAITING) { this.setState({ currentState: IDLING, from: from, to: to }); } } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { cancelAnimationFrame(this.raf); } }, { key: 'render', value: function render() { return _react2.default.createElement( _reactMotion.Motion, _extends({}, this.getMotionProps(), { onRest: this.onRest }), this.renderContent ); } }]); return Collapse; }(_react2.default.PureComponent); Collapse.defaultProps = { springConfig: {}, forceInitialAnimation: false, hasNestedCollapse: false, animateContentChanges: true, fixedHeight: -1, style: {}, theme: css, onRender: noop, onRest: noop, onMeasure: noop, getContentHeight: getContentHeightDefault }; var _initialiseProps = function _initialiseProps() { var _this2 = this; this.onContentRef = function (content) { _this2.content = content; }; this.onWrapperRef = function (wrapper) { _this2.wrapper = wrapper; }; this.onRest = function () { _this2.raf = requestAnimationFrame(_this2.setResting); }; this.setResting = function () { _this2.currentHeight = undefined; _this2.setState({ currentState: RESTING, resizingFrom: undefined }); }; this.getTo = function () { var _props4 = _this2.props, fixedHeight = _props4.fixedHeight, getContentHeight = _props4.getContentHeight; return fixedHeight > -1 ? fixedHeight : getContentHeight(_this2.content, _this2.wrapper); }; this.getWrapperStyle = function (height) { if (_this2.state.currentState === IDLING && _this2.state.to) { var fixedHeight = _this2.props.fixedHeight; if (fixedHeight > -1) { return { overflow: 'hidden', height: fixedHeight }; } return { height: 'auto' }; } if (_this2.state.currentState === WAITING && !_this2.state.to) { return { overflow: 'hidden', height: 0 }; } return { overflow: 'hidden', height: Math.max(0, height) }; }; this.getMotionProps = function () { var springConfig = _this2.props.springConfig; return _this2.state.currentState === IDLING ? { // When completely stable, instantly jump to the position defaultStyle: { height: _this2.state.to }, style: { height: _this2.state.to } } : { // Otherwise, animate defaultStyle: { height: _this2.state.from }, style: { height: (0, _reactMotion.spring)(_this2.state.to, _extends({ precision: SPRING_PRECISION }, springConfig)) } }; }; this.renderContent = function (_ref) { var height = _ref.height; // eslint-disable-line var _props5 = _this2.props, _isOpened = _props5.isOpened, _springConfig = _props5.springConfig, _forceInitialAnimation = _props5.forceInitialAnimation, _animateContentChanges = _props5.animateContentChanges, _hasNestedCollapse = _props5.hasNestedCollapse, _fixedHeight = _props5.fixedHeight, theme = _props5.theme, style = _props5.style, onRender = _props5.onRender, _onRest = _props5.onRest, _onMeasure = _props5.onMeasure, _getContentHeight = _props5.getContentHeight, children = _props5.children, props = _objectWithoutProperties(_props5, ['isOpened', 'springConfig', 'forceInitialAnimation', 'animateContentChanges', 'hasNestedCollapse', 'fixedHeight', 'theme', 'style', 'onRender', 'onRest', 'onMeasure', 'getContentHeight', 'children']); var _state2 = _this2.state, from = _state2.from, to = _state2.to, currentState = _state2.currentState; if (currentState === RESIZING) { _this2.currentHeight = height; } // DANGEROUS, use with caution, never do setState with it onRender({ current: height, from: from, to: to }); return _react2.default.createElement( 'div', _extends({ ref: _this2.onWrapperRef, className: theme.collapse, style: _extends({}, _this2.getWrapperStyle(Math.max(0, height)), style) }, props), _react2.default.createElement( 'div', { ref: _this2.onContentRef, className: theme.content }, children ) ); }; };