UNPKG

20.4 kBJavaScriptView Raw
1'use strict';
2
3exports.__esModule = true;
4exports.default = undefined;
5
6var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
7
8var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
9
10var _typeof2 = require('babel-runtime/helpers/typeof');
11
12var _typeof3 = _interopRequireDefault(_typeof2);
13
14var _class, _temp, _initialiseProps;
15
16var _util = require('../../util');
17
18var _findNode = require('./find-node');
19
20var _findNode2 = _interopRequireDefault(_findNode);
21
22function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
24var VIEWPORT = 'viewport';
25
26// IE8 not support pageXOffset
27var getPageX = function getPageX() {
28 return window.pageXOffset || document.documentElement.scrollLeft;
29};
30var getPageY = function getPageY() {
31 return window.pageYOffset || document.documentElement.scrollTop;
32};
33
34/**
35 * @private get element size
36 * @param {Element} element
37 * @return {Object}
38 */
39function _getSize(element) {
40 // element like `svg` do not have offsetWidth and offsetHeight prop
41 // then getBoundingClientRect
42 if ('offsetWidth' in element && 'offsetHeight' in element) {
43 return {
44 width: element.offsetWidth,
45 height: element.offsetHeight
46 };
47 } else {
48 var _element$getBoundingC = element.getBoundingClientRect(),
49 width = _element$getBoundingC.width,
50 height = _element$getBoundingC.height;
51
52 return {
53 width: width,
54 height: height
55 };
56 }
57}
58
59/**
60 * @private get element rect
61 * @param {Element} elem
62 * @return {Object}
63 */
64function _getElementRect(elem, container) {
65 var offsetTop = 0,
66 offsetLeft = 0,
67 scrollTop = 0,
68 scrollLeft = 0;
69
70 var _getSize2 = _getSize(elem),
71 width = _getSize2.width,
72 height = _getSize2.height;
73
74 do {
75 if (!isNaN(elem.offsetTop)) {
76 offsetTop += elem.offsetTop;
77 }
78 if (!isNaN(elem.offsetLeft)) {
79 offsetLeft += elem.offsetLeft;
80 }
81 if (elem && elem.offsetParent) {
82 if (!isNaN(elem.offsetParent.scrollLeft) && elem.offsetParent !== document.body) {
83 scrollLeft += elem.offsetParent.scrollLeft;
84 }
85
86 if (!isNaN(elem.offsetParent.scrollTop) && elem.offsetParent !== document.body) {
87 scrollTop += elem.offsetParent.scrollTop;
88 }
89 }
90
91 elem = elem.offsetParent;
92 } while (elem !== null && elem !== container);
93
94 // if container is body or invalid, treat as window, use client width & height
95 var treatAsWindow = !container || container === document.body;
96
97 return {
98 top: offsetTop - scrollTop - (treatAsWindow ? document.documentElement.scrollTop || document.body.scrollTop : 0),
99 left: offsetLeft - scrollLeft - (treatAsWindow ? document.documentElement.scrollLeft || document.body.scrollLeft : 0),
100 width: width,
101 height: height
102 };
103}
104
105/**
106 * @private get viewport size
107 * @return {Object}
108 */
109function _getViewportSize(container) {
110 if (!container || container === document.body) {
111 return {
112 width: document.documentElement.clientWidth,
113 height: document.documentElement.clientHeight
114 };
115 }
116
117 var _container$getBoundin = container.getBoundingClientRect(),
118 width = _container$getBoundin.width,
119 height = _container$getBoundin.height;
120
121 return {
122 width: width,
123 height: height
124 };
125}
126
127var getContainer = function getContainer(_ref) {
128 var container = _ref.container,
129 baseElement = _ref.baseElement;
130
131 // SSR下会有副作用
132 if ((typeof document === 'undefined' ? 'undefined' : (0, _typeof3.default)(document)) === undefined) {
133 return container;
134 }
135
136 var calcContainer = (0, _findNode2.default)(container, baseElement);
137
138 if (!calcContainer) {
139 calcContainer = document.body;
140 }
141
142 while (_util.dom.getStyle(calcContainer, 'position') === 'static') {
143 if (!calcContainer || calcContainer === document.body) {
144 return document.body;
145 }
146 calcContainer = calcContainer.parentNode;
147 }
148
149 return calcContainer;
150};
151
152var Position = (_temp = _class = function () {
153 function Position(props) {
154 (0, _classCallCheck3.default)(this, Position);
155
156 _initialiseProps.call(this);
157
158 this.pinElement = props.pinElement;
159 this.baseElement = props.baseElement;
160 this.pinFollowBaseElementWhenFixed = props.pinFollowBaseElementWhenFixed;
161 this.container = getContainer(props);
162 this.autoFit = props.autoFit || false;
163 this.align = props.align || 'tl tl';
164 this.offset = props.offset || [0, 0];
165 this.needAdjust = props.needAdjust || false;
166 this.isRtl = props.isRtl || false;
167 }
168
169 /**
170 * @public static place method
171 * @param {Object} props
172 * @param {DOM} props.pinElement
173 * @param {DOM} props.baseElement
174 * @param {String} props.align
175 * @param {Number} props.offset
176 * @param {Boolean} props.needAdjust
177 * @param {Boolean} props.isRtl
178 * @return {Position}
179 */
180
181
182 Position.prototype.setPosition = function setPosition() {
183 var pinElement = this.pinElement;
184 var baseElement = this.baseElement;
185 var pinFollowBaseElementWhenFixed = this.pinFollowBaseElementWhenFixed;
186 var expectedAlign = this._getExpectedAlign();
187 var isPinFixed = void 0,
188 isBaseFixed = void 0,
189 firstPositionResult = void 0;
190 if (pinElement === VIEWPORT) {
191 return;
192 }
193 if (_util.dom.getStyle(pinElement, 'position') !== 'fixed') {
194 _util.dom.setStyle(pinElement, 'position', 'absolute');
195 isPinFixed = false;
196 } else {
197 isPinFixed = true;
198 }
199 if (baseElement === VIEWPORT || _util.dom.getStyle(baseElement, 'position') !== 'fixed') {
200 isBaseFixed = false;
201 } else {
202 isBaseFixed = true;
203 }
204
205 // 根据期望的定位
206 for (var i = 0; i < expectedAlign.length; i++) {
207 var align = expectedAlign[i];
208 var pinElementPoints = this._normalizePosition(pinElement, align.split(' ')[0], isPinFixed);
209 var baseElementPoints = this._normalizePosition(baseElement, align.split(' ')[1],
210 // 忽略元素位置,发生在类似dialog的场景下
211 isPinFixed && !pinFollowBaseElementWhenFixed);
212
213 var pinElementParentOffset = this._getParentOffset(pinElement);
214 var pinElementParentScrollOffset = this._getParentScrollOffset(pinElement);
215
216 var baseElementOffset = isPinFixed && isBaseFixed ? this._getLeftTop(baseElement) : // 在 pin 是 fixed 布局,并且又需要根据 base 计算位置时,计算 base 的 offset 需要忽略页面滚动
217 baseElementPoints.offset(isPinFixed && pinFollowBaseElementWhenFixed);
218 var top = baseElementOffset.top + baseElementPoints.y - pinElementParentOffset.top - pinElementPoints.y + pinElementParentScrollOffset.top;
219 var left = baseElementOffset.left + baseElementPoints.x - pinElementParentOffset.left - pinElementPoints.x + pinElementParentScrollOffset.left;
220
221 this._setPinElementPostion(pinElement, { left: left, top: top }, this.offset);
222
223 if (this._isInViewport(pinElement, align)) {
224 return align;
225 } else if (!firstPositionResult) {
226 if (this.needAdjust && !this.autoFit) {
227 var _getViewportOffset2 = this._getViewportOffset(pinElement, align),
228 right = _getViewportOffset2.right;
229
230 firstPositionResult = {
231 left: right < 0 ? left + right : left,
232 top: top
233 };
234 } else {
235 firstPositionResult = { left: left, top: top };
236 }
237 }
238 }
239
240 // This will only execute if `pinElement` could not be placed entirely in the Viewport
241 var inViewportLeft = this._makeElementInViewport(pinElement, firstPositionResult.left, 'Left', isPinFixed);
242 var inViewportTop = this._makeElementInViewport(pinElement, firstPositionResult.top, 'Top', isPinFixed);
243
244 this._setPinElementPostion(pinElement, { left: inViewportLeft, top: inViewportTop }, this._calPinOffset(expectedAlign[0]));
245
246 return expectedAlign[0];
247 };
248
249 Position.prototype._getParentOffset = function _getParentOffset(element) {
250 var parent = element.offsetParent || document.documentElement;
251 var offset = void 0;
252 if (parent === document.body && _util.dom.getStyle(parent, 'position') === 'static') {
253 offset = {
254 top: 0,
255 left: 0
256 };
257 } else {
258 offset = this._getElementOffset(parent);
259 }
260
261 offset.top += parseFloat(_util.dom.getStyle(parent, 'border-top-width'), 10);
262 offset.left += parseFloat(_util.dom.getStyle(parent, 'border-left-width'), 10);
263 offset.offsetParent = parent;
264 return offset;
265 };
266
267 Position.prototype._makeElementInViewport = function _makeElementInViewport(pinElement, number, type, isPinFixed) {
268 // pinElement.offsetParent is never body because wrapper has position: absolute
269 // refactored to make code clearer. Revert if wrapper style changes.
270 var result = number;
271 var docElement = document.documentElement;
272 var offsetParent = pinElement.offsetParent || document.documentElement;
273
274 if (result < 0) {
275 if (isPinFixed) {
276 result = 0;
277 } else if (offsetParent === document.body && _util.dom.getStyle(offsetParent, 'position') === 'static') {
278 // Only when div's offsetParent is document.body, we set new position result.
279 result = Math.max(docElement['scroll' + type], document.body['scroll' + type]);
280 }
281 }
282 return result;
283 };
284
285 // 这里的第三个参数真实含义为:是否为fixed布局,并且像dialog一样,不跟随trigger元素
286
287
288 Position.prototype._normalizePosition = function _normalizePosition(element, align, ignoreElementOffset) {
289 var points = this._normalizeElement(element, ignoreElementOffset);
290 this._normalizeXY(points, align);
291
292 return points;
293 };
294
295 Position.prototype._normalizeXY = function _normalizeXY(points, align) {
296 var x = align.split('')[1];
297 var y = align.split('')[0];
298
299 points.x = this._xyConverter(x, points, 'width');
300 points.y = this._xyConverter(y, points, 'height');
301
302 return points;
303 };
304
305 Position.prototype._xyConverter = function _xyConverter(align, points, type) {
306 var res = align.replace(/t|l/gi, '0%').replace(/c/gi, '50%').replace(/b|r/gi, '100%').replace(/(\d+)%/gi, function (m, d) {
307 return points.size()[type] * (d / 100);
308 });
309
310 return parseFloat(res, 10) || 0;
311 };
312
313 Position.prototype._getLeftTop = function _getLeftTop(element) {
314 return {
315 left: parseFloat(_util.dom.getStyle(element, 'left')) || 0,
316 top: parseFloat(_util.dom.getStyle(element, 'top')) || 0
317 };
318 };
319
320 Position.prototype._normalizeElement = function _normalizeElement(element, ignoreElementOffset) {
321 var _this = this;
322
323 var result = {
324 element: element,
325 x: 0,
326 y: 0
327 },
328 isViewport = element === VIEWPORT,
329 docElement = document.documentElement;
330
331 result.offset = function (ignoreScroll) {
332 // 这里是关键,第二个参数的含义以ing该是:是否为 fixed 布局,并且像 dialog 一样,不跟随 trigger 元素
333 if (ignoreElementOffset) {
334 return {
335 left: 0,
336 top: 0
337 };
338 } else if (isViewport) {
339 return {
340 left: getPageX(),
341 top: getPageY()
342 };
343 } else {
344 return _this._getElementOffset(element, ignoreScroll);
345 }
346 };
347
348 result.size = function () {
349 if (isViewport) {
350 return {
351 width: docElement.clientWidth,
352 height: docElement.clientHeight
353 };
354 } else {
355 return _getSize(element);
356 }
357 };
358
359 return result;
360 };
361
362 // ignoreScroll 在 pin 元素为 fixed 的时候生效,此时需要忽略页面滚动
363 // 对 fixed 模式下 subNav 弹层的计算很重要,只有在这种情况下,才同时需要元素的相对位置,又不关心页面滚动
364
365
366 Position.prototype._getElementOffset = function _getElementOffset(element, ignoreScroll) {
367 var rect = element.getBoundingClientRect();
368 var docElement = document.documentElement;
369 var body = document.body;
370 var docClientLeft = docElement.clientLeft || body.clientLeft || 0;
371 var docClientTop = docElement.clientTop || body.clientTop || 0;
372
373 return {
374 left: rect.left + (ignoreScroll ? 0 : getPageX()) - docClientLeft,
375 top: rect.top + (ignoreScroll ? 0 : getPageY()) - docClientTop
376 };
377 };
378
379 // According to the location of the overflow to calculate the desired positioning
380
381
382 Position.prototype._getExpectedAlign = function _getExpectedAlign() {
383 var align = this.isRtl ? this._replaceAlignDir(this.align, /l|r/g, { l: 'r', r: 'l' }) : this.align;
384 var expectedAlign = [align];
385 if (this.needAdjust) {
386 if (/t|b/g.test(align)) {
387 expectedAlign.push(this._replaceAlignDir(align, /t|b/g, { t: 'b', b: 't' }));
388 }
389 if (/l|r/g.test(align)) {
390 expectedAlign.push(this._replaceAlignDir(align, /l|r/g, { l: 'r', r: 'l' }));
391 }
392 if (/c/g.test(align)) {
393 expectedAlign.push(this._replaceAlignDir(align, /c(?= |$)/g, { c: 'l' }));
394 expectedAlign.push(this._replaceAlignDir(align, /c(?= |$)/g, { c: 'r' }));
395 }
396 expectedAlign.push(this._replaceAlignDir(align, /l|r|t|b/g, {
397 l: 'r',
398 r: 'l',
399 t: 'b',
400 b: 't'
401 }));
402 }
403 return expectedAlign;
404 };
405
406 // Transform align order.
407
408
409 Position.prototype._replaceAlignDir = function _replaceAlignDir(align, regExp, map) {
410 return align.replace(regExp, function (res) {
411 return map[res];
412 });
413 };
414
415 // Are the right sides of the pin and base aligned?
416
417
418 Position.prototype._isRightAligned = function _isRightAligned(align) {
419 var _align$split = align.split(' '),
420 pinAlign = _align$split[0],
421 baseAlign = _align$split[1];
422
423 return pinAlign[1] === 'r' && pinAlign[1] === baseAlign[1];
424 };
425
426 // Are the bottoms of the pin and base aligned?
427
428
429 Position.prototype._isBottomAligned = function _isBottomAligned(align) {
430 var _align$split2 = align.split(' '),
431 pinAlign = _align$split2[0],
432 baseAlign = _align$split2[1];
433
434 return pinAlign[0] === 'b' && pinAlign[0] === baseAlign[0];
435 };
436
437 // Detecting element is in the window, we want to adjust position later.
438
439
440 Position.prototype._isInViewport = function _isInViewport(element, align) {
441 var viewportSize = _getViewportSize(this.container);
442 var elementRect = _getElementRect(element, this.container);
443 var elementSize = _getSize(element);
444
445 // https://github.com/alibaba-fusion/next/issues/853
446 // Equality causes issues in Chrome when pin element is off screen to right or bottom.
447 // If it is not supposed to align with the bottom or right, then subtract 1 to use strict less than.
448 var viewportWidth = this._isRightAligned(align) ? viewportSize.width : viewportSize.width - 1;
449 var viewportHeight = this._isBottomAligned(align) ? viewportSize.height : viewportSize.height - 1;
450
451 // 临时方案,在 select + table 的场景下,不需要关注横向上是否在可视区域内
452 // 在 balloon 场景下需要关注
453 if (this.autoFit) {
454 return elementRect.top >= 0 && elementRect.top + element.offsetHeight <= viewportHeight;
455 }
456
457 // Avoid animate problem that use offsetWidth instead of getBoundingClientRect.
458 return elementRect.left >= 0 && elementRect.left + elementSize.width <= viewportWidth && elementRect.top >= 0 && elementRect.top + elementSize.height <= viewportHeight;
459 };
460
461 Position.prototype._getViewportOffset = function _getViewportOffset(element, align) {
462 var viewportSize = _getViewportSize(this.container);
463 var elementRect = _getElementRect(element, this.container);
464 var elementSize = _getSize(element);
465
466 var viewportWidth = this._isRightAligned(align) ? viewportSize.width : viewportSize.width - 1;
467 var viewportHeight = this._isBottomAligned(align) ? viewportSize.height : viewportSize.height - 1;
468
469 return {
470 top: elementRect.top,
471 right: viewportWidth - (elementRect.left + elementSize.width),
472 bottom: viewportHeight - (elementRect.top + elementSize.height),
473 left: elementRect.left
474 };
475 };
476
477 // 在这里做RTL判断 top-left 定位转化为等效的 top-right定位
478
479
480 Position.prototype._setPinElementPostion = function _setPinElementPostion(pinElement, postion) {
481 var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
482 var top = postion.top,
483 left = postion.left;
484
485 if (!this.isRtl) {
486 _util.dom.setStyle(pinElement, {
487 left: left + offset[0] + 'px',
488 top: top + offset[1] + 'px'
489 });
490 return;
491 }
492
493 // transfer {left,top} equaly to {right,top}
494 var pinElementParentOffset = this._getParentOffset(pinElement);
495
496 var _getElementRect2 = _getElementRect(pinElementParentOffset.offsetParent),
497 offsetParentWidth = _getElementRect2.width;
498
499 var _getElementRect3 = _getElementRect(pinElement),
500 width = _getElementRect3.width;
501
502 var right = offsetParentWidth - (left + width);
503 _util.dom.setStyle(pinElement, {
504 left: 'auto',
505 right: right + offset[0] + 'px',
506 top: top + offset[1] + 'px'
507 });
508 };
509
510 return Position;
511}(), _class.VIEWPORT = VIEWPORT, _class.place = function (props) {
512 return new Position(props).setPosition();
513}, _initialiseProps = function _initialiseProps() {
514 var _this2 = this;
515
516 this._calPinOffset = function (align) {
517 var offset = [].concat(_this2.offset);
518
519 if (_this2.autoFit && align && _this2.container && _this2.container !== document.body) {
520 var baseElementRect = _getElementRect(_this2.baseElement, _this2.container);
521 var pinElementRect = _getElementRect(_this2.pinElement, _this2.container);
522 var viewportSize = _getViewportSize(_this2.container);
523 var pinAlign = align.split(' ')[0];
524 var x = pinAlign.charAt(1);
525 var y = pinAlign.charAt(0);
526
527 if (pinElementRect.top < 0 || pinElementRect.top + pinElementRect.height > viewportSize.height) {
528 offset[1] = -baseElementRect.top - (y === 't' ? baseElementRect.height : 0);
529 }
530 }
531 return offset;
532 };
533
534 this._getParentScrollOffset = function (elem) {
535 var top = 0;
536 var left = 0;
537
538 if (elem && elem.offsetParent && elem.offsetParent !== document.body) {
539 if (!isNaN(elem.offsetParent.scrollTop)) {
540 top += elem.offsetParent.scrollTop;
541 }
542 if (!isNaN(elem.offsetParent.scrollLeft)) {
543 left += elem.offsetParent.scrollLeft;
544 }
545 }
546
547 return {
548 top: top,
549 left: left
550 };
551 };
552}, _temp);
553exports.default = Position;
554module.exports = exports['default'];
\No newline at end of file