import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; var _class, _temp, _initialiseProps; import { dom } from '../../util'; var VIEWPORT = 'viewport'; // IE8 not support pageXOffset var getPageX = function getPageX() { return window.pageXOffset || document.documentElement.scrollLeft; }; var getPageY = function getPageY() { return window.pageYOffset || document.documentElement.scrollTop; }; /** * @private get element rect * @param {Element} elem * @return {Object} */ function _getElementRect(elem, container) { var offsetTop = 0, offsetLeft = 0, scrollTop = 0, scrollLeft = 0; var offsetHeight = elem.offsetHeight; var offsetWidth = elem.offsetWidth; do { if (!isNaN(elem.offsetTop)) { offsetTop += elem.offsetTop; } if (!isNaN(elem.offsetLeft)) { offsetLeft += elem.offsetLeft; } if (elem && elem.offsetParent) { if (!isNaN(elem.offsetParent.scrollLeft) && elem.offsetParent !== document.body) { scrollLeft += elem.offsetParent.scrollLeft; } if (!isNaN(elem.offsetParent.scrollTop) && elem.offsetParent !== document.body) { scrollTop += elem.offsetParent.scrollTop; } } elem = elem.offsetParent; } while (elem !== null && elem !== container); // if container is body or invalid, treat as window, use client width & height var treatAsWindow = !container || container === document.body; return { top: offsetTop - scrollTop - (treatAsWindow ? document.documentElement.scrollTop || document.body.scrollTop : 0), left: offsetLeft - scrollLeft - (treatAsWindow ? document.documentElement.scrollLeft || document.body.scrollLeft : 0), height: offsetHeight, width: offsetWidth }; } /** * @private get viewport size * @return {Object} */ function _getViewportSize(container) { if (!container || container === document.body) { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }; } var _container$getBoundin = container.getBoundingClientRect(), width = _container$getBoundin.width, height = _container$getBoundin.height; return { width: width, height: height }; } var getContainer = function getContainer(_ref) { var container = _ref.container, autoFit = _ref.autoFit, baseElement = _ref.baseElement; var calcContainer = void 0; if (typeof container === 'string') { calcContainer = document.getElementById(container); } else if (typeof container === 'function') { calcContainer = container(baseElement); } else { return document.body; } if (!autoFit) { return calcContainer; } while (dom.getStyle(calcContainer, 'position') === 'static') { if (!calcContainer || calcContainer === document.body) { return document.body; } calcContainer = calcContainer.parentNode; } return calcContainer; }; var Position = (_temp = _class = function () { function Position(props) { _classCallCheck(this, Position); _initialiseProps.call(this); this.pinElement = props.pinElement; this.baseElement = props.baseElement; this.container = getContainer(props); this.autoFit = props.autoFit || false; this.align = props.align || 'tl tl'; this.offset = props.offset || [0, 0]; this.needAdjust = props.needAdjust || false; this.isRtl = props.isRtl || false; } /** * @public static place method * @param {Object} props * @param {DOM} props.pinElement * @param {DOM} props.baseElement * @param {String} props.align * @param {Number} props.offset * @param {Boolean} props.needAdjust * @param {Boolean} props.isRtl * @return {Position} */ Position.prototype.setPosition = function setPosition() { var pinElement = this.pinElement; var baseElement = this.baseElement; var expectedAlign = this._getExpectedAlign(); var isPinFixed = void 0, isBaseFixed = void 0, firstPositionResult = void 0; if (pinElement === VIEWPORT) { return; } if (dom.getStyle(pinElement, 'position') !== 'fixed') { dom.setStyle(pinElement, 'position', 'absolute'); isPinFixed = false; } else { isPinFixed = true; } if (baseElement === VIEWPORT || dom.getStyle(baseElement, 'position') !== 'fixed') { isBaseFixed = false; } else { isBaseFixed = true; } // 根据期望的定位 for (var i = 0; i < expectedAlign.length; i++) { var align = expectedAlign[i]; var pinElementPoints = this._normalizePosition(pinElement, align.split(' ')[0], isPinFixed); var baseElementPoints = this._normalizePosition(baseElement, align.split(' ')[1], isPinFixed); var pinElementParentOffset = this._getParentOffset(pinElement); var pinElementParentScrollOffset = this._getParentScrollOffset(pinElement); var baseElementOffset = isPinFixed && isBaseFixed ? this._getLeftTop(baseElement) : baseElementPoints.offset(); var top = baseElementOffset.top + baseElementPoints.y - pinElementParentOffset.top - pinElementPoints.y + pinElementParentScrollOffset.top; var left = baseElementOffset.left + baseElementPoints.x - pinElementParentOffset.left - pinElementPoints.x + pinElementParentScrollOffset.left; this._setPinElementPostion(pinElement, { left: left, top: top }, this.offset); if (!firstPositionResult) { firstPositionResult = { left: left, top: top }; } if (this._isInViewport(pinElement, align)) { return align; } } // This will only execute if `pinElement` could not be placed entirely in the Viewport var inViewportLeft = this._makeElementInViewport(pinElement, firstPositionResult.left, 'Left', isPinFixed); var inViewportTop = this._makeElementInViewport(pinElement, firstPositionResult.top, 'Top', isPinFixed); this._setPinElementPostion(pinElement, { left: inViewportLeft, top: inViewportTop }, this._calPinOffset(expectedAlign[0])); return expectedAlign[0]; }; Position.prototype._getParentOffset = function _getParentOffset(element) { var parent = element.offsetParent || document.documentElement; var offset = void 0; if (parent === document.body && dom.getStyle(parent, 'position') === 'static') { offset = { top: 0, left: 0 }; } else { offset = this._getElementOffset(parent); } offset.top += parseFloat(dom.getStyle(parent, 'border-top-width'), 10); offset.left += parseFloat(dom.getStyle(parent, 'border-left-width'), 10); offset.offsetParent = parent; return offset; }; Position.prototype._makeElementInViewport = function _makeElementInViewport(pinElement, number, type, isPinFixed) { // pinElement.offsetParent is never body because wrapper has position: absolute // refactored to make code clearer. Revert if wrapper style changes. var result = number; var docElement = document.documentElement; var offsetParent = pinElement.offsetParent || document.documentElement; if (result < 0) { if (isPinFixed) { result = 0; } else if (offsetParent === document.body && dom.getStyle(offsetParent, 'position') === 'static') { // Only when div's offsetParent is document.body, we set new position result. result = Math.max(docElement['scroll' + type], document.body['scroll' + type]); } } return result; }; Position.prototype._normalizePosition = function _normalizePosition(element, align, isPinFixed) { var points = this._normalizeElement(element, isPinFixed); this._normalizeXY(points, align); return points; }; Position.prototype._normalizeXY = function _normalizeXY(points, align) { var x = align.split('')[1]; var y = align.split('')[0]; points.x = this._xyConverter(x, points, 'width'); points.y = this._xyConverter(y, points, 'height'); return points; }; Position.prototype._xyConverter = function _xyConverter(align, points, type) { var res = align.replace(/t|l/gi, '0%').replace(/c/gi, '50%').replace(/b|r/gi, '100%').replace(/(\d+)%/gi, function (m, d) { return points.size()[type] * (d / 100); }); return parseFloat(res, 10) || 0; }; Position.prototype._getLeftTop = function _getLeftTop(element) { return { left: parseFloat(dom.getStyle(element, 'left')) || 0, top: parseFloat(dom.getStyle(element, 'top')) || 0 }; }; Position.prototype._normalizeElement = function _normalizeElement(element, isPinFixed) { var _this = this; var result = { element: element, x: 0, y: 0 }, isViewport = element === VIEWPORT, docElement = document.documentElement; result.offset = function () { if (isPinFixed) { return { left: 0, top: 0 }; } else if (isViewport) { return { left: getPageX(), top: getPageY() }; } else { return _this._getElementOffset(element); } }; result.size = function () { if (isViewport) { return { width: docElement.clientWidth, height: docElement.clientHeight }; } else { return { width: element.offsetWidth, height: element.offsetHeight }; } }; return result; }; Position.prototype._getElementOffset = function _getElementOffset(element) { var rect = element.getBoundingClientRect(); var docElement = document.documentElement; var body = document.body; var docClientLeft = docElement.clientLeft || body.clientLeft || 0; var docClientTop = docElement.clientTop || body.clientTop || 0; return { left: rect.left + (getPageX() - docClientLeft), top: rect.top + (getPageY() - docClientTop) }; }; // According to the location of the overflow to calculate the desired positioning Position.prototype._getExpectedAlign = function _getExpectedAlign() { var align = this.isRtl ? this._replaceAlignDir(this.align, /l|r/g, { l: 'r', r: 'l' }) : this.align; var expectedAlign = [align]; if (this.needAdjust) { if (/t|b/g.test(align)) { expectedAlign.push(this._replaceAlignDir(align, /t|b/g, { t: 'b', b: 't' })); } if (/l|r/g.test(align)) { expectedAlign.push(this._replaceAlignDir(align, /l|r/g, { l: 'r', r: 'l' })); } if (/c/g.test(align)) { expectedAlign.push(this._replaceAlignDir(align, /c(?= |$)/g, { c: 'l' })); expectedAlign.push(this._replaceAlignDir(align, /c(?= |$)/g, { c: 'r' })); } expectedAlign.push(this._replaceAlignDir(align, /l|r|t|b/g, { l: 'r', r: 'l', t: 'b', b: 't' })); } return expectedAlign; }; // Transform align order. Position.prototype._replaceAlignDir = function _replaceAlignDir(align, regExp, map) { return align.replace(regExp, function (res) { return map[res]; }); }; // Are the right sides of the pin and base aligned? Position.prototype._isRightAligned = function _isRightAligned(align) { var _align$split = align.split(' '), pinAlign = _align$split[0], baseAlign = _align$split[1]; return pinAlign[1] === 'r' && pinAlign[1] === baseAlign[1]; }; // Are the bottoms of the pin and base aligned? Position.prototype._isBottomAligned = function _isBottomAligned(align) { var _align$split2 = align.split(' '), pinAlign = _align$split2[0], baseAlign = _align$split2[1]; return pinAlign[0] === 'b' && pinAlign[0] === baseAlign[0]; }; // Detecting element is in the window, we want to adjust position later. Position.prototype._isInViewport = function _isInViewport(element, align) { var viewportSize = _getViewportSize(this.container); var elementRect = _getElementRect(element, this.container); // https://github.com/alibaba-fusion/next/issues/853 // Equality causes issues in Chrome when pin element is off screen to right or bottom. // If it is not supposed to align with the bottom or right, then subtract 1 to use strict less than. var viewportWidth = this._isRightAligned(align) ? viewportSize.width : viewportSize.width - 1; var viewportHeight = this._isBottomAligned(align) ? viewportSize.height : viewportSize.height - 1; // 临时方案,在 select + table 的场景下,不需要关注横向上是否在可视区域内 // 在 balloon 场景下需要关注 if (this.autoFit) { return elementRect.top >= 0 && elementRect.top + element.offsetHeight <= viewportHeight; } // Avoid animate problem that use offsetWidth instead of getBoundingClientRect. return elementRect.left >= 0 && elementRect.left + element.offsetWidth <= viewportWidth && elementRect.top >= 0 && elementRect.top + element.offsetHeight <= viewportHeight; }; // 在这里做RTL判断 top-left 定位转化为等效的 top-right定位 Position.prototype._setPinElementPostion = function _setPinElementPostion(pinElement, postion) { var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0]; var top = postion.top, left = postion.left; if (!this.isRtl) { dom.setStyle(pinElement, { left: left + offset[0] + 'px', top: top + offset[1] + 'px' }); return; } // transfer {left,top} equaly to {right,top} var pinElementParentOffset = this._getParentOffset(pinElement); var _getElementRect2 = _getElementRect(pinElementParentOffset.offsetParent), offsetParentWidth = _getElementRect2.width; var _getElementRect3 = _getElementRect(pinElement), width = _getElementRect3.width; var right = offsetParentWidth - (left + width); dom.setStyle(pinElement, { left: 'auto', right: right + offset[0] + 'px', top: top + offset[1] + 'px' }); }; return Position; }(), _class.VIEWPORT = VIEWPORT, _class.place = function (props) { return new Position(props).setPosition(); }, _initialiseProps = function _initialiseProps() { var _this2 = this; this._calPinOffset = function (align) { var offset = [].concat(_this2.offset); if (_this2.autoFit && align && _this2.container && _this2.container !== document.body) { var baseElementRect = _getElementRect(_this2.baseElement, _this2.container); var pinElementRect = _getElementRect(_this2.pinElement, _this2.container); var viewportSize = _getViewportSize(_this2.container); var pinAlign = align.split(' ')[0]; var x = pinAlign.charAt(1); var y = pinAlign.charAt(0); if (pinElementRect.top < 0 || pinElementRect.top + pinElementRect.height > viewportSize.height) { offset[1] = -baseElementRect.top - (y === 't' ? baseElementRect.height : 0); } } return offset; }; this._getParentScrollOffset = function (elem) { var top = 0; var left = 0; if (elem && elem.offsetParent && elem.offsetParent !== document.body) { if (!isNaN(elem.offsetParent.scrollTop)) { top += elem.offsetParent.scrollTop; } if (!isNaN(elem.offsetParent.scrollLeft)) { left += elem.offsetParent.scrollLeft; } } return { top: top, left: left }; }; }, _temp); export { Position as default };