"use strict";

exports.__esModule = true;
exports["default"] = void 0;
var _ReflexSplitter = _interopRequireDefault(require("./ReflexSplitter"));
var _ReflexEvents = _interopRequireDefault(require("./ReflexEvents"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _reactDom = _interopRequireDefault(require("react-dom"));
var _react = _interopRequireDefault(require("react"));
var _lodash = require("lodash");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
var ReflexContainer = /*#__PURE__*/function (_React$Component) {
  _inheritsLoose(ReflexContainer, _React$Component);
  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  function ReflexContainer(props) {
    var _this;
    _this = _React$Component.call(this, props) || this;
    _this.state = {
      flexData: []
    };
    _this.events = new _ReflexEvents["default"]();
    _this.onSplitterStartResize = _this.onSplitterStartResize.bind(_assertThisInitialized(_this));
    _this.onSplitterStopResize = _this.onSplitterStopResize.bind(_assertThisInitialized(_this));
    _this.onSplitterResize = _this.onSplitterResize.bind(_assertThisInitialized(_this));
    _this.onElementSize = _this.onElementSize.bind(_assertThisInitialized(_this));
    _this.children = [];
    return _this;
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  var _proto = ReflexContainer.prototype;
  _proto.setPartialState = function setPartialState(partialState) {
    var _this2 = this;
    return new Promise(function (resolve) {
      _this2.setState(Object.assign({}, _this2.state, partialState), function () {
        resolve();
      });
    });
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.componentDidMount = function componentDidMount() {
    var flexData = this.computeFlexData();
    this.setPartialState({
      flexData: flexData
    });
    this.events.on("splitter.startResize", this.onSplitterStartResize);
    this.events.on("splitter.stopResize", this.onSplitterStopResize);
    this.events.on("splitter.resize", this.onSplitterResize);
    this.events.on("element.size", this.onElementSize);
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.componentWillUnmount = function componentWillUnmount() {
    this.events.off();
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.getValidChildren = function getValidChildren(props) {
    if (props === void 0) {
      props = this.props;
    }
    return this.toArray(props.children).filter(function (child) {
      return !!child;
    });
  }

  /////////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////////
  ;
  _proto.UNSAFE_componentWillReceiveProps = function UNSAFE_componentWillReceiveProps(props) {
    var children = this.getValidChildren(props);
    if (children.length !== this.state.flexData.length || this.flexHasChanged(props)) {
      var flexData = this.computeFlexData(children);
      this.setPartialState({
        flexData: flexData
      });
    }
  }

  /////////////////////////////////////////////////////////
  // Check if flex has changed: this allows updating the
  // component when different flex is passed as property
  // to one or several children
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.flexHasChanged = function flexHasChanged(props) {
    var nextChildrenFlex = this.getValidChildren(props).map(function (child) {
      return child.props.flex || 0;
    });
    var childrenFlex = this.getValidChildren().map(function (child) {
      return child.props.flex || 0;
    });
    return !childrenFlex.every(function (flex, idx) {
      return flex === nextChildrenFlex[idx];
    });
  }

  /////////////////////////////////////////////////////////
  // Returns size of a ReflexElement
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.getSize = function getSize(element) {
    var ref = element.ref ? this.refs[element.ref] : element;
    var domElement = _reactDom["default"].findDOMNode(ref);
    switch (this.props.orientation) {
      case "horizontal":
        return domElement.offsetHeight;
      case "vertical":
      default:
        return domElement.offsetWidth;
    }
  }

  /////////////////////////////////////////////////////////
  // Computes offset from pointer position
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.getOffset = function getOffset(event) {
    var pos = event.changedTouches ? event.changedTouches[0] : event;
    switch (this.props.orientation) {
      case "horizontal":
        return pos.pageY - this.previousPos;
      case "vertical":
      default:
        return pos.pageX - this.previousPos;
    }
  }

  /////////////////////////////////////////////////////////
  // Handles splitter startResize event
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.onSplitterStartResize = function onSplitterStartResize(data) {
    var pos = data.event.changedTouches ? data.event.changedTouches[0] : data.event;
    switch (this.props.orientation) {
      case "horizontal":
        document.body.style.cursor = "row-resize";
        this.previousPos = pos.pageY;
        break;
      case "vertical":
      default:
        document.body.style.cursor = "col-resize";
        this.previousPos = pos.pageX;
        break;
    }
    var idx = data.splitter.props.index;
    this.elements = [this.children[idx - 1], this.children[idx + 1]];
    this.emitElementsEvent(this.elements, "onStartResize");
  }

  /////////////////////////////////////////////////////////
  // Handles splitter resize event
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.onSplitterResize = function onSplitterResize(data) {
    var _this3 = this;
    var idx = data.splitter.props.index;
    var offset = this.getOffset(data.event);
    var availableOffset = this.computeAvailableOffset(idx, offset);
    if (this.hasCollapsed) {
      if (this.isNegativeWhenCollapsing ? offset > 0 : offset < 0) {
        this.hasCollapsed = false;
        this.setPartialState(this.stateBeforeCollapse).then(function () {
          _this3.emitElementsEvent(_this3.elements, "onResize");
        });
      }
      return;
    }
    if (!availableOffset) {
      this.closeThreshold = 40;
      var shrink = this.computeAvailableShrink(idx, offset);
      if (shrink === 0 && Math.abs(offset) > this.closeThreshold) {
        var childIdx = offset > 0 ? idx + 1 : idx - 1;
        var child = this.children[childIdx];
        var size = this.getSize(child);
        this.stateBeforeCollapse = (0, _lodash.cloneDeep)(this.state);
        this.isNegativeWhenCollapsing = offset < 0;
        availableOffset = size * (offset > 0 ? 1 : -1);
        this.hasCollapsed = child.props;
        this.elements = this.dispatchOffset(idx, availableOffset);
        this.adjustFlex(this.elements);
        this.setPartialState(this.state).then(function () {
          _this3.emitElementsEvent(_this3.elements, "onResize");
        });
      }
    } else if (availableOffset) {
      var pos = data.event.changedTouches ? data.event.changedTouches[0] : data.event;
      switch (this.props.orientation) {
        case "horizontal":
          this.previousPos = pos.pageY;
          break;
        case "vertical":
        default:
          this.previousPos = pos.pageX;
          break;
      }
      this.elements = this.dispatchOffset(idx, availableOffset);
      this.adjustFlex(this.elements);
      this.setPartialState(this.state).then(function () {
        _this3.emitElementsEvent(_this3.elements, "onResize");
      });
    }
  }

  /////////////////////////////////////////////////////////
  // Determines if element is a splitter
  // or wraps a splitter
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.isSplitterElement = function isSplitterElement(element) {
    //https://github.com/leefsmp/Re-Flex/issues/49
    return process.env.NODE_ENV === "development" ? element.type === /*#__PURE__*/_react["default"].createElement(_ReflexSplitter["default"], null).type : element.type === _ReflexSplitter["default"];
  }

  /////////////////////////////////////////////////////////
  // Handles splitter stopResize event
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.onSplitterStopResize = function onSplitterStopResize(data) {
    var _this4 = this;
    if (this.hasCollapsed) {
      this.props.onPanelCollapse && this.props.onPanelCollapse(this.hasCollapsed);
      this.hasCollapsed = false;
    }
    document.body.style.cursor = "auto";
    var resizedRefs = this.elements.map(function (element) {
      return element.ref;
    });
    var elements = this.children.filter(function (child) {
      return !_this4.isSplitterElement(child) && resizedRefs.includes(child.ref);
    });
    this.emitElementsEvent(elements, "onStopResize");
  }

  /////////////////////////////////////////////////////////
  // Handles element size modified event
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.onElementSize = function onElementSize(data) {
    var _this5 = this;
    return new Promise(function (resolve) {
      try {
        var idx = data.element.props.index;
        var size = _this5.getSize(_this5.children[idx]);
        var offset = data.size - size;
        var dir = data.direction;
        var splitterIdx = idx + dir;
        var availableOffset = _this5.computeAvailableOffset(splitterIdx, dir * offset);
        _this5.elements = null;
        if (availableOffset) {
          _this5.elements = _this5.dispatchOffset(splitterIdx, availableOffset);
          _this5.adjustFlex(_this5.elements);
        }
        _this5.setPartialState(_this5.state).then(function () {
          _this5.emitElementsEvent(_this5.elements, "onResize");
          resolve();
        });
      } catch (ex) {
        // TODO handle exception ...
      }
    });
  }

  /////////////////////////////////////////////////////////
  // Adjusts flex after a dispatch to make sure
  // total flex of modified elements remains the same
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.adjustFlex = function adjustFlex(elements) {
    var _this6 = this;
    var diffFlex = elements.reduce(function (sum, element) {
      var idx = element.props.index;
      var previousFlex = element.props.flex;
      var nextFlex = _this6.state.flexData[idx].flex;
      return sum + (previousFlex - nextFlex) / elements.length;
    }, 0);
    elements.forEach(function (element) {
      _this6.state.flexData[element.props.index].flex += diffFlex;
    });
  }

  /////////////////////////////////////////////////////////
  // Returns available offset for a given raw offset value
  // This checks how much the panes can be stretched and
  // shrink, then returns the min
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.computeAvailableOffset = function computeAvailableOffset(idx, offset) {
    var stretch = this.computeAvailableStretch(idx, offset);
    var shrink = this.computeAvailableShrink(idx, offset);
    var availableOffset = Math.min(stretch, shrink) * Math.sign(offset);
    return availableOffset;
  }

  /////////////////////////////////////////////////////////
  // Returns true if the next splitter than the one at idx
  // can propagate the drag. This can happen if that
  // next element is actually a splitter and it has
  // propagate=true property set
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.checkPropagate = function checkPropagate(idx, direction) {
    if (direction > 0) {
      if (idx < this.children.length - 2) {
        var child = this.children[idx + 2];
        var typeCheck = this.isSplitterElement(child);
        return typeCheck && child.props.propagate;
      }
    } else {
      if (idx > 2) {
        var _child = this.children[idx - 2];
        var _typeCheck = this.isSplitterElement(_child);
        return _typeCheck && _child.props.propagate;
      }
    }
    return false;
  }

  /////////////////////////////////////////////////////////
  // Recursively computes available stretch at splitter
  // idx for given raw offset
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.computeAvailableStretch = function computeAvailableStretch(idx, offset) {
    var childIdx = offset < 0 ? idx + 1 : idx - 1;
    var child = this.children[childIdx];
    var size = this.getSize(child);
    var maxSize = child.props.maxSize;
    var availableStretch = maxSize - size;
    if (availableStretch < Math.abs(offset)) {
      if (this.checkPropagate(idx, -1 * offset)) {
        var nextOffset = Math.sign(offset) * (Math.abs(offset) - availableStretch);
        return availableStretch + this.computeAvailableStretch(offset < 0 ? idx + 2 : idx - 2, nextOffset);
      }
    }
    return Math.min(availableStretch, Math.abs(offset));
  }

  /////////////////////////////////////////////////////////
  // Recursively computes available shrink at splitter
  // idx for given raw offset
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.computeAvailableShrink = function computeAvailableShrink(idx, offset) {
    var childIdx = offset > 0 ? idx + 1 : idx - 1;
    var child = this.children[childIdx];
    var size = this.getSize(child);
    var minSize = Math.max(child.props.minSize, 0);
    var availableShrink = size - minSize;
    if (availableShrink < Math.abs(offset)) {
      if (this.checkPropagate(idx, offset)) {
        var nextOffset = Math.sign(offset) * (Math.abs(offset) - availableShrink);
        return availableShrink + this.computeAvailableShrink(offset > 0 ? idx + 2 : idx - 2, nextOffset);
      }
    }
    return Math.min(availableShrink, Math.abs(offset));
  }

  /////////////////////////////////////////////////////////
  // Returns flex value for unit pixel
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.computePixelFlex = function computePixelFlex() {
    var domElement = _reactDom["default"].findDOMNode(this);
    switch (this.props.orientation) {
      case "horizontal":
        if (domElement.offsetHeight === 0.0) {
          console.warn("Found ReflexContainer with height=0, " + "this will cause invalid behavior...");
          console.warn(domElement);
          return 0.0;
        }
        return 1.0 / domElement.offsetHeight;
      case "vertical":
      default:
        if (domElement.offsetWidth === 0.0) {
          console.warn("Found ReflexContainer with width=0, " + "this will cause invalid behavior...");
          console.warn(domElement);
          return 0.0;
        }
        return 1.0 / domElement.offsetWidth;
    }
  }

  /////////////////////////////////////////////////////////
  // Adds offset to a given ReflexElement
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.addOffset = function addOffset(element, offset) {
    var size = this.getSize(element);
    var idx = element.props.index;
    var newSize = Math.max(size + offset, 0);
    var currentFlex = this.state.flexData[idx].flex;
    var newFlex = currentFlex > 0 ? currentFlex * newSize / size : this.computePixelFlex() * newSize;
    this.state.flexData[idx].flex = !isFinite(newFlex) || isNaN(newFlex) ? 0 : newFlex;
  }

  /////////////////////////////////////////////////////////
  // Recursively dispatches stretch offset across
  // children elements starting at splitter idx
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.dispatchStretch = function dispatchStretch(idx, offset) {
    var childIdx = offset < 0 ? idx + 1 : idx - 1;
    if (childIdx < 0 || childIdx > this.children.length - 1) {
      return [];
    }
    var child = this.children[childIdx];
    var size = this.getSize(child);
    var newSize = Math.min(child.props.maxSize, size + Math.abs(offset));
    var dispatchedStretch = newSize - size;
    this.addOffset(child, dispatchedStretch);
    if (dispatchedStretch < Math.abs(offset)) {
      var nextIdx = idx - Math.sign(offset) * 2;
      var nextOffset = Math.sign(offset) * (Math.abs(offset) - dispatchedStretch);
      return [child].concat(this.dispatchStretch(nextIdx, nextOffset));
    }
    return [child];
  }

  /////////////////////////////////////////////////////////
  // Recursively dispatches shrink offset across
  // children elements starting at splitter idx
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.dispatchShrink = function dispatchShrink(idx, offset) {
    var childIdx = offset > 0 ? idx + 1 : idx - 1;
    if (childIdx < 0 || childIdx > this.children.length - 1) {
      return [];
    }
    var child = this.children[childIdx];
    var size = this.getSize(child);
    var newSize = Math.max(
    // child.props.minSize, //tnr: not sure what to do about this.. I had to comment this out to make the collapsing work
    size - Math.abs(offset));
    var dispatchedShrink = newSize - size;
    this.addOffset(child, dispatchedShrink);
    if (Math.abs(dispatchedShrink) < Math.abs(offset)) {
      var nextIdx = idx + Math.sign(offset) * 2;
      var nextOffset = Math.sign(offset) * (Math.abs(offset) + dispatchedShrink);
      return [child].concat(this.dispatchShrink(nextIdx, nextOffset));
    }
    return [child];
  }

  /////////////////////////////////////////////////////////
  // Dispatch offset at splitter idx
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.dispatchOffset = function dispatchOffset(idx, offset) {
    return [].concat(this.dispatchStretch(idx, offset), this.dispatchShrink(idx, offset));
  }

  /////////////////////////////////////////////////////////
  // Emits given if event for each given element
  // if present in the component props
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.emitElementsEvent = function emitElementsEvent(elements, event) {
    var _this7 = this;
    this.toArray(elements).forEach(function (element) {
      if (element.props[event]) {
        var ref = _this7.refs[element.ref];
        element.props[event]({
          domElement: _reactDom["default"].findDOMNode(ref),
          component: element
        });
      }
    });
  }

  /////////////////////////////////////////////////////////
  // Computes initial flex data based on provided flex
  // properties. By default each ReflexElement gets
  // evenly arranged within its container
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.computeFlexData = function computeFlexData(children) {
    var _this8 = this;
    if (children === void 0) {
      children = this.getValidChildren();
    }
    var pixelFlex = this.computePixelFlex();
    var computeFreeFlex = function computeFreeFlex(flexData) {
      return flexData.reduce(function (sum, entry) {
        if (!_this8.isSplitterElement(entry) && entry.constrained) {
          return sum - entry.flex;
        }
        return sum;
      }, 1);
    };
    var computeFreeElements = function computeFreeElements(flexData) {
      return flexData.reduce(function (sum, entry) {
        if (!_this8.isSplitterElement(entry) && !entry.constrained) {
          return sum + 1;
        }
        return sum;
      }, 0);
    };
    var flexDataInit = children.map(function (child) {
      var props = child.props;
      return {
        maxFlex: (props.maxSize || Number.MAX_VALUE) * pixelFlex,
        sizeFlex: (props.size || Number.MAX_VALUE) * pixelFlex,
        minFlex: (props.minSize || 1) * pixelFlex,
        constrained: props.flex !== undefined,
        guid: props.ref || _this8.guid(),
        flex: props.flex || 0,
        type: child.type
      };
    });
    var computeFlexDataRec = function computeFlexDataRec(flexDataIn) {
      var hasContrain = false;
      var freeElements = computeFreeElements(flexDataIn);
      var freeFlex = computeFreeFlex(flexDataIn);
      var flexDataOut = flexDataIn.map(function (entry) {
        if (_this8.isSplitterElement(entry)) {
          return entry;
        }
        var proposedFlex = !entry.constrained ? freeFlex / freeElements : entry.flex;
        var constrainedFlex = Math.min(entry.sizeFlex, Math.min(entry.maxFlex, Math.max(entry.minFlex, proposedFlex)));
        var constrained = constrainedFlex !== proposedFlex;
        hasContrain = hasContrain || constrained;
        return Object.assign({}, entry, {
          flex: constrainedFlex,
          constrained: constrained
        });
      });
      return hasContrain ? computeFlexDataRec(flexDataOut) : flexDataOut;
    };
    var flexData = computeFlexDataRec(flexDataInit);
    return flexData.map(function (entry) {
      return {
        flex: !_this8.isSplitterElement(entry) ? entry.flex : 0.0,
        guid: entry.guid
      };
    });
  }

  /////////////////////////////////////////////////////////
  // Utility method that generates a new unique GUID
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.guid = function guid(format) {
    if (format === void 0) {
      format = "xxxx-xxxx";
    }
    var d = new Date().getTime();
    return format.replace(/[xy]/g, function (c) {
      var r = (d + Math.random() * 16) % 16 | 0;
      d = Math.floor(d / 16);
      return (c == "x" ? r : r & 0x7 | 0x8).toString(16);
    });
  }

  /////////////////////////////////////////////////////////
  // Utility method to ensure given argument is
  // returned as an array
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.toArray = function toArray(obj) {
    return obj ? Array.isArray(obj) ? obj : [obj] : [];
  }

  /////////////////////////////////////////////////////////
  // Render container. This will clone all original child
  // components in order to pass some internal properties
  // used to handle resizing logic
  //
  /////////////////////////////////////////////////////////
  ;
  _proto.render = function render() {
    var _this9 = this;
    var classNames = ["reflex-layout", "reflex-container", this.props.orientation].concat(this.props.className.split(" "));
    this.children = _react["default"].Children.map(this.getValidChildren(), function (child, idx) {
      if (idx > _this9.state.flexData.length - 1) {
        return /*#__PURE__*/_react["default"].createElement("div", null);
      }
      var flexData = _this9.state.flexData[idx];
      var newProps = Object.assign({}, child.props, {
        maxSize: child.props.maxSize || Number.MAX_VALUE,
        orientation: _this9.props.orientation,
        minSize: child.props.minSize || 1,
        events: _this9.events,
        flex: (0, _lodash.round)(flexData.flex, 5),
        //tnr: this rounding is necessary because flex was getting computed very slightly off (eg -1.423432e-17). This corrects for that
        ref: flexData.guid,
        index: idx
      });
      return _react["default"].cloneElement(child, newProps);
    });
    return /*#__PURE__*/_react["default"].createElement("div", {
      className: classNames.join(" "),
      style: this.props.style
    }, this.children);
  };
  return ReflexContainer;
}(_react["default"].Component); /////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
ReflexContainer.propTypes = process.env.NODE_ENV !== "production" ? {
  orientation: _propTypes["default"].oneOf(["horizontal", "vertical"]),
  className: _propTypes["default"].string,
  style: _propTypes["default"].object
} : {};

/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
ReflexContainer.defaultProps = {
  orientation: "horizontal",
  className: "",
  style: {}
};
var _default = ReflexContainer;
exports["default"] = _default;
module.exports = exports.default;