'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ConnectedShadow = undefined;

var _extends2 = require('babel-runtime/helpers/extends');

var _extends3 = _interopRequireDefault(_extends2);

var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');

var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);

var _values = require('babel-runtime/core-js/object/values');

var _values2 = _interopRequireDefault(_values);

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactDom = require('react-dom');

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

var _lodash = require('lodash.throttle');

var _lodash2 = _interopRequireDefault(_lodash);

var _Sentinel = require('./Sentinel');

var _Sentinel2 = _interopRequireDefault(_Sentinel);

var _ShadowOverlay = require('./ShadowOverlay');

var _ShadowOverlay2 = _interopRequireDefault(_ShadowOverlay);

var _DefaultContainer = require('./DefaultContainer');

var _DefaultContainer2 = _interopRequireDefault(_DefaultContainer);

var _DefaultScrollContainer = require('./DefaultScrollContainer');

var _DefaultScrollContainer2 = _interopRequireDefault(_DefaultScrollContainer);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var _createContext = (0, _react.createContext)(function () {
  return function () {};
}),
    Provider = _createContext.Provider,
    Consumer = _createContext.Consumer;

/**
 * A scrolling container which has a shadow effect to indicate that there is more
 * content above or below. This element also creates a context which can inform
 * components below it in the tree of the scroll state of the scrolling container.
 */


var ScrollShadow = function (_React$Component) {
  (0, _inherits3.default)(ScrollShadow, _React$Component);

  function ScrollShadow() {
    var _ref;

    var _temp, _this, _ret;

    (0, _classCallCheck3.default)(this, ScrollShadow);

    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = ScrollShadow.__proto__ || (0, _getPrototypeOf2.default)(ScrollShadow)).call.apply(_ref, [this].concat(args))), _this), _this.topSentinelRef = (0, _react.createRef)(), _this.bottomSentinelRef = (0, _react.createRef)(), _this.isTopSentinelVisible = true, _this.isBottomSentinelVisible = false, _this.calcShadowMode = function () {
      var top = !_this.isTopSentinelVisible;
      var bottom = !_this.isBottomSentinelVisible;

      if (top && bottom) {
        return 'both';
      } else if (top) {
        return 'top';
      } else if (bottom) {
        return 'bottom';
      } else {
        return 'none';
      }
    }, _this.handleSentinelIntersection = function (entries, observer) {
      // `entries` is an array of records, one per changed sentinel.
      entries.forEach(function (entry) {
        if (entry.target === _this.topSentinelRef.current) {
          _this.isTopSentinelVisible = entry.isIntersecting;
        } else if (entry.target === _this.bottomSentinelRef.current) {
          _this.isBottomSentinelVisible = entry.isIntersecting;
        }

        var currentMode = _this.calcShadowMode();

        /**
         * Iterate over every registered shadow element to update their styles
         * if they need to change.
         */
        (0, _values2.default)(_this.shadowElementRegistrations).forEach(function (registration) {
          var ref = registration.ref,
              mode = registration.mode;
          // we don't want to recalculate styles needlessly, so we also ensure
          // the mode has changed since last style assignment

          if (ref.current && mode !== currentMode) {
            // reset classes
            ref.current.classList.remove('top', 'bottom', 'both');
            ref.current.classList.add(currentMode);
            registration.mode = currentMode;
          }
        });
      });
    }, _this.scrollElementRef = function (el) {
      if (_this.sentinelIntersectionObserver) {
        _this.sentinelIntersectionObserver.disconnect();
      }

      _this.sentinelIntersectionObserver = new window.IntersectionObserver(_this.handleSentinelIntersection, {
        root: el
      });
      _this.sentinelIntersectionObserver.observe(_this.topSentinelRef.current);
      _this.sentinelIntersectionObserver.observe(_this.bottomSentinelRef.current);
    }, _this.shadowElementRegistrations = {}, _this.getShadowElementRef = function (name) {
      if (!_this.shadowElementRegistrations[name]) {
        _this.shadowElementRegistrations[name] = {
          ref: (0, _react.createRef)()
        };
      }

      return _this.shadowElementRegistrations[name].ref;
    }, _this.renderGlobalSentinels = function () {
      return (0, _reactDom.createPortal)(_react2.default.createElement(
        _react2.default.Fragment,
        null,
        _react2.default.createElement(_Sentinel2.default, {
          innerRef: _this.topSentinelRef,
          className: 'scroll-shadow-sentinel top global',
          debugShow: _this.props.debugShowSentinels
        }),
        _react2.default.createElement(_Sentinel2.default, {
          innerRef: _this.bottomSentinelRef,
          className: 'scroll-shadow-sentinel bottom global',
          debugShow: _this.props.debugShowSentinels
        })
      ), window.document.body);
    }, _this.renderChildren = function () {
      var _this$props = _this.props,
          children = _this$props.children,
          global = _this$props.global,
          Container = _this$props.Container,
          ScrollContainer = _this$props.ScrollContainer,
          disabled = _this$props.disabled,
          outer = _this$props.outer,
          extraProps = (0, _objectWithoutProperties3.default)(_this$props, ['children', 'global', 'Container', 'ScrollContainer', 'disabled', 'outer']);

      // we will be rendering something slightly different if we are in global mode

      if (global) {
        return _react2.default.createElement(
          _react2.default.Fragment,
          null,
          children,
          _this.renderGlobalSentinels(),
          !disabled && _react2.default.createElement(_ShadowOverlay2.default, {
            className: 'scroll-shadow inner',
            entireViewport: true,
            innerRef: _this.getShadowElementRef('built-in')
          })
        );
      }

      var shadowPlacement = outer ? 'outer' : 'inner';

      return _react2.default.createElement(
        Container,
        (0, _extends3.default)({ className: 'scroll-shadow-container' }, extraProps),
        _react2.default.createElement(
          ScrollContainer,
          (0, _extends3.default)({ innerRef: _this.scrollElementRef }, extraProps),
          _react2.default.createElement(_Sentinel2.default, {
            innerRef: _this.topSentinelRef,
            className: 'scroll-shadow-sentinel top',
            debugShow: _this.props.debugShowSentinels
          }),
          children,
          _react2.default.createElement(_Sentinel2.default, {
            innerRef: _this.bottomSentinelRef,
            className: 'scroll-shadow-sentinel bottom',
            debugShow: _this.props.debugShowSentinels
          })
        ),
        !disabled && _react2.default.createElement(_ShadowOverlay2.default, {
          className: 'scroll-shadow ' + shadowPlacement,
          innerRef: _this.getShadowElementRef('built-in')
        })
      );
    }, _temp), (0, _possibleConstructorReturn3.default)(_this, _ret);
  }

  /**
   * A sentinel is an invisible element which is used to track the scroll boundaries.
   * We place them at the top and bottom of the content. When either of them becomes
   * visible, we know that the user has hit that particular scroll boundary.
   */

  /**
   * We are not storing this information in this.state on purpose. We will actually
   * be skipping the React update cycle entirely for changing the shadow styles
   * on the shadow element to avoid unnecessary work for a simple aesthetic change.
   */


  (0, _createClass3.default)(ScrollShadow, [{
    key: 'componentDidMount',
    value: function componentDidMount() {
      if (this.props.global) {
        /**
         * In global mode, the entire window is our scroll context. We can do that by
         * passing 'null' as the context element for the IntersectionObserver.
         * As documented, it will use the visible window area as the intersection
         * boundary.
         */
        this.scrollElementRef(null);
      }
    }

    /**
     * Simply figures out where the shadows should be based on which boundaries
     * are visible right now.
     */


    /**
     * This will be called every time the intersection state of either sentinel changes.
     */


    /**
     * A map of elements which have registered that they want a shadow applied to them.
     * Elements can register by using the `getShadowElementRef` function we pass through
     * the Context.
     */

  }, {
    key: 'render',
    value: function render() {
      return _react2.default.createElement(
        Provider,
        { value: this.getShadowElementRef },
        this.renderChildren()
      );
    }
  }]);
  return ScrollShadow;
}(_react2.default.Component);

/**
 * A ConnectedShadow is a shadow element which gets its shadow mode from
 * a ScrollShadow context higher in the React tree. Using this, you can
 * add some shadows to 'unrelated' elements which respond to the
 * scroll state of the containing scroll element. This is good for
 * advanced use cases, like rendering an element which hovers over
 * a scroll container.
 */


ScrollShadow.propTypes = {
  /**
   * This ScrollShadow should listen to the global window scroll value. Only one
   * such ScrollShadow should be present in your application.
   */
  global: _propTypes2.default.bool,
  /**
   * Customize the outer container element by passing a component here. Your element
   * MUST have relative or absolute positioning applied!
   */
  Container: _propTypes2.default.func,
  /**
   * Customize the scroll container element by passing a component here. Your element
   * MUST have relative or absolute positioning, an overflow mode, and accept an
   * innerRef prop!
   */
  ScrollContainer: _propTypes2.default.func,
  /**
   * Flip this to true to show a red line where the scroll sentinel elements are.
   * Can be useful for debugging if the shadows don't seem to work correctly.
   */
  debugShowSentinels: _propTypes2.default.bool
};
ScrollShadow.defaultProps = {
  global: false,
  Container: _DefaultContainer2.default,
  ScrollContainer: _DefaultScrollContainer2.default,
  debugShowSentinels: false
};
exports.default = ScrollShadow;

var ConnectedShadow = exports.ConnectedShadow = function (_React$PureComponent) {
  (0, _inherits3.default)(ConnectedShadow, _React$PureComponent);

  function ConnectedShadow() {
    var _ref2;

    var _temp2, _this2, _ret2;

    (0, _classCallCheck3.default)(this, ConnectedShadow);

    for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
      args[_key2] = arguments[_key2];
    }

    return _ret2 = (_temp2 = (_this2 = (0, _possibleConstructorReturn3.default)(this, (_ref2 = ConnectedShadow.__proto__ || (0, _getPrototypeOf2.default)(ConnectedShadow)).call.apply(_ref2, [this].concat(args))), _this2), _this2.generatedName = 'shadow' + Math.floor(Math.random() * 100000), _temp2), (0, _possibleConstructorReturn3.default)(_this2, _ret2);
  }

  (0, _createClass3.default)(ConnectedShadow, [{
    key: 'render',
    value: function render() {
      var _props = this.props,
          outer = _props.outer,
          entireViewport = _props.entireViewport,
          disabled = _props.disabled,
          _props$name = _props.name,
          name = _props$name === undefined ? this.generatedName : _props$name;


      if (disabled) {
        return null;
      }

      return _react2.default.createElement(
        Consumer,
        null,
        function (getShadowElementRef) {
          return _react2.default.createElement(_ShadowOverlay2.default, {
            className: 'scroll-shadow ' + (outer ? 'outer' : 'inner'),
            entireViewport: entireViewport,
            innerRef: getShadowElementRef(name)
          });
        }
      );
    }
  }]);
  return ConnectedShadow;
}(_react2.default.PureComponent);

ScrollShadow.ConnectedShadow = ConnectedShadow;