@txdfe/at
Version:
一个设计体系组件库
508 lines (490 loc) • 21.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _util = require("../../util");
var _findNode = _interopRequireDefault(require("./find-node"));
var _Position;
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
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;
var offsetLeft = 0;
var scrollTop = 0;
var scrollLeft = 0;
var _elem = elem,
offsetHeight = _elem.offsetHeight;
var _elem2 = elem,
offsetWidth = _elem2.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 = (0, _findNode["default"])(container, baseElement);
if (!calcContainer) {
calcContainer = document.body;
}
if (!autoFit) {
return calcContainer;
}
while (_util.dom.getStyle(calcContainer, 'position') === 'static') {
if (!calcContainer || calcContainer === document.body) {
return document.body;
}
calcContainer = calcContainer.parentNode;
}
return calcContainer;
};
var Position = exports["default"] = /*#__PURE__*/function () {
function Position(props) {
var _this = this;
_classCallCheck(this, Position);
_defineProperty(this, "_calPinOffset", function (align) {
var offset = _toConsumableArray(_this.offset);
if (_this.autoFit && align && _this.container && _this.container !== document.body) {
var baseElementRect = _getElementRect(_this.baseElement, _this.container);
var pinElementRect = _getElementRect(_this.pinElement, _this.container);
var viewportSize = _getViewportSize(_this.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;
});
_defineProperty(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
};
});
this.pinElement = props.pinElement;
this.baseElement = props.baseElement;
this.pinFollowBaseElementWhenFixed = props.pinFollowBaseElementWhenFixed;
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;
}
return _createClass(Position, [{
key: "setPosition",
value: function setPosition() {
var pinElement = this.pinElement;
var baseElement = this.baseElement;
var pinFollowBaseElementWhenFixed = this.pinFollowBaseElementWhenFixed;
var expectedAlign = this._getExpectedAlign();
var isPinFixed;
var isBaseFixed;
var firstPositionResult;
if (pinElement === VIEWPORT) {
return;
}
if (_util.dom.getStyle(pinElement, 'position') !== 'fixed') {
_util.dom.setStyle(pinElement, 'position', 'absolute');
isPinFixed = false;
} else {
isPinFixed = true;
}
if (baseElement === VIEWPORT || _util.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],
// 忽略元素位置,发生在类似dialog的场景下
isPinFixed && !pinFollowBaseElementWhenFixed);
var pinElementParentOffset = this._getParentOffset(pinElement);
var pinElementParentScrollOffset = this._getParentScrollOffset(pinElement);
var baseElementOffset = isPinFixed && isBaseFixed ? this._getLeftTop(baseElement) :
// 在 pin 是 fixed 布局,并且又需要根据 base 计算位置时,计算 base 的 offset 需要忽略页面滚动
baseElementPoints.offset(isPinFixed && pinFollowBaseElementWhenFixed);
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._setPinElementPosition(pinElement, {
left: _left,
top: _top
}, this.offset);
if (!firstPositionResult) {
firstPositionResult = {
left: _left,
top: _top
};
}
if (this._isInViewport(pinElement, align)) {
return align;
}
}
var _firstPositionResult = firstPositionResult,
left = _firstPositionResult.left,
top = _firstPositionResult.top;
this._setPinElementPosition(pinElement, {
left: left,
top: top
}, this._calPinOffset(expectedAlign[0]));
if (!isPinFixed && this.needAdjust) {
var viewportSize = _getViewportSize(this.container);
var elementRect = _getElementRect(pinElement, this.container);
var viewportWidth = viewportSize.width;
var viewportHeight = viewportSize.height;
var yOverflow = elementRect.top + pinElement.offsetHeight - viewportHeight;
var xOverflow = elementRect.left + pinElement.offsetWidth - viewportWidth;
left -= Math.max(xOverflow, 0);
top -= Math.max(yOverflow, 0);
}
this._setPinElementPosition(pinElement, {
left: left,
top: top
}, this._calPinOffset(expectedAlign[0]));
return expectedAlign[0];
}
}, {
key: "_getParentOffset",
value: function _getParentOffset(element) {
var parent = element.offsetParent || document.documentElement;
var offset;
if (parent === document.body && _util.dom.getStyle(parent, 'position') === 'static') {
offset = {
top: 0,
left: 0
};
} else {
offset = this._getElementOffset(parent);
}
offset.top += parseFloat(_util.dom.getStyle(parent, 'border-top-width'), 10);
offset.left += parseFloat(_util.dom.getStyle(parent, 'border-left-width'), 10);
offset.offsetParent = parent;
return offset;
}
}, {
key: "_makeElementInViewport",
value: 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 && _util.dom.getStyle(offsetParent, 'position') === 'static') {
// Only when div's offsetParent is document.body, we set new position result.
result = Math.max(docElement["scroll".concat(type)], document.body["scroll".concat(type)]);
}
}
return result;
}
// 这里的第三个参数真实含义为:是否为fixed布局,并且像dialog一样,不跟随trigger元素
}, {
key: "_normalizePosition",
value: function _normalizePosition(element, align, ignoreElementOffset) {
var points = this._normalizeElement(element, ignoreElementOffset);
this._normalizeXY(points, align);
return points;
}
}, {
key: "_normalizeXY",
value: 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;
}
}, {
key: "_xyConverter",
value: 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;
}
}, {
key: "_getLeftTop",
value: function _getLeftTop(element) {
return {
left: parseFloat(_util.dom.getStyle(element, 'left')) || 0,
top: parseFloat(_util.dom.getStyle(element, 'top')) || 0
};
}
}, {
key: "_normalizeElement",
value: function _normalizeElement(element, ignoreElementOffset) {
var _this2 = this;
var result = {
element: element,
x: 0,
y: 0
};
var isViewport = element === VIEWPORT;
var docElement = document.documentElement;
result.offset = function (ignoreScroll) {
// 这里是关键,第二个参数的含义以ing该是:是否为 fixed 布局,并且像 dialog 一样,不跟随 trigger 元素
if (ignoreElementOffset) {
return {
left: 0,
top: 0
};
} else if (isViewport) {
return {
left: getPageX(),
top: getPageY()
};
} else {
return _this2._getElementOffset(element, ignoreScroll);
}
};
result.size = function () {
if (isViewport) {
return {
width: docElement.clientWidth,
height: docElement.clientHeight
};
} else {
return {
width: element.offsetWidth,
height: element.offsetHeight
};
}
};
return result;
}
// ignoreScroll 在 pin 元素为 fixed 的时候生效,此时需要忽略页面滚动
// 对 fixed 模式下 subNav 弹层的计算很重要,只有在这种情况下,才同时需要元素的相对位置,又不关心页面滚动
}, {
key: "_getElementOffset",
value: function _getElementOffset(element, ignoreScroll) {
var rect = element.getBoundingClientRect();
var docElement = document.documentElement;
var _document = document,
body = _document.body;
var docClientLeft = docElement.clientLeft || body.clientLeft || 0;
var docClientTop = docElement.clientTop || body.clientTop || 0;
return {
left: rect.left + (ignoreScroll ? 0 : getPageX()) - docClientLeft,
top: rect.top + (ignoreScroll ? 0 : getPageY()) - docClientTop
};
}
// According to the location of the overflow to calculate the desired positioning
}, {
key: "_getExpectedAlign",
value: 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.
}, {
key: "_replaceAlignDir",
value: function _replaceAlignDir(align, regExp, map) {
return align.replace(regExp, function (res) {
return map[res];
});
}
// Are the right sides of the pin and base aligned?
}, {
key: "_isRightAligned",
value: function _isRightAligned(align) {
var _align$split = align.split(' '),
_align$split2 = _slicedToArray(_align$split, 2),
pinAlign = _align$split2[0],
baseAlign = _align$split2[1];
return pinAlign[1] === 'r' && pinAlign[1] === baseAlign[1];
}
// Are the bottoms of the pin and base aligned?
}, {
key: "_isBottomAligned",
value: function _isBottomAligned(align) {
var _align$split3 = align.split(' '),
_align$split4 = _slicedToArray(_align$split3, 2),
pinAlign = _align$split4[0],
baseAlign = _align$split4[1];
return pinAlign[0] === 'b' && pinAlign[0] === baseAlign[0];
}
// Detecting element is in the window, we want to adjust position later.
}, {
key: "_isInViewport",
value: function _isInViewport(element, align) {
var viewportSize = _getViewportSize(this.container);
var elementRect = element.getBoundingClientRect();
// 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.y >= 0 && elementRect.y + elementRect.height <= viewportHeight;
}
// Avoid animate problem that use offsetWidth instead of getBoundingClientRect.
return elementRect.x >= 0 && elementRect.x + elementRect.width <= viewportWidth && elementRect.y >= 0 && elementRect.y + elementRect.height <= viewportHeight;
}
// 在这里做RTL判断 top-left 定位转化为等效的 top-right定位
}, {
key: "_setPinElementPosition",
value: function _setPinElementPosition(pinElement, position) {
var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
var top = position.top,
left = position.left;
if (!this.isRtl) {
_util.dom.setStyle(pinElement, {
left: "".concat(left + offset[0], "px"),
top: "".concat(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);
_util.dom.setStyle(pinElement, {
left: 'auto',
right: "".concat(right + offset[0], "px"),
top: "".concat(top + offset[1], "px")
});
}
}]);
}();
_Position = Position;
_defineProperty(Position, "VIEWPORT", VIEWPORT);
/**
* @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}
*/
_defineProperty(Position, "place", function (props) {
return new _Position(props).setPosition();
});