1 | // @flow
|
---|
2 | import getWindow from './getWindow';
|
---|
3 | import getNodeName from './getNodeName';
|
---|
4 | import getComputedStyle from './getComputedStyle';
|
---|
5 | import { isHTMLElement, isShadowRoot } from './instanceOf';
|
---|
6 | import isTableElement from './isTableElement';
|
---|
7 | import getParentNode from './getParentNode';
|
---|
8 | import getUAString from '../utils/userAgent';
|
---|
9 |
|
---|
10 | function getTrueOffsetParent(element: Element): ?Element {
|
---|
11 | if (
|
---|
12 | !isHTMLElement(element) ||
|
---|
13 | // https://github.com/popperjs/popper-core/issues/837
|
---|
14 | getComputedStyle(element).position === 'fixed'
|
---|
15 | ) {
|
---|
16 | return null;
|
---|
17 | }
|
---|
18 |
|
---|
19 | return element.offsetParent;
|
---|
20 | }
|
---|
21 |
|
---|
22 | // `.offsetParent` reports `null` for fixed elements, while absolute elements
|
---|
23 | // return the containing block
|
---|
24 | function getContainingBlock(element: Element) {
|
---|
25 | const isFirefox = /firefox/i.test(getUAString());
|
---|
26 | const isIE = /Trident/i.test(getUAString());
|
---|
27 |
|
---|
28 | if (isIE && isHTMLElement(element)) {
|
---|
29 | // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport
|
---|
30 | const elementCss = getComputedStyle(element);
|
---|
31 | if (elementCss.position === 'fixed') {
|
---|
32 | return null;
|
---|
33 | }
|
---|
34 | }
|
---|
35 |
|
---|
36 | let currentNode = getParentNode(element);
|
---|
37 |
|
---|
38 | if (isShadowRoot(currentNode)) {
|
---|
39 | currentNode = currentNode.host;
|
---|
40 | }
|
---|
41 |
|
---|
42 | while (
|
---|
43 | isHTMLElement(currentNode) &&
|
---|
44 | ['html', 'body'].indexOf(getNodeName(currentNode)) < 0
|
---|
45 | ) {
|
---|
46 | const css = getComputedStyle(currentNode);
|
---|
47 |
|
---|
48 | // This is non-exhaustive but covers the most common CSS properties that
|
---|
49 | // create a containing block.
|
---|
50 | // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
|
---|
51 | if (
|
---|
52 | css.transform !== 'none' ||
|
---|
53 | css.perspective !== 'none' ||
|
---|
54 | css.contain === 'paint' ||
|
---|
55 | ['transform', 'perspective'].indexOf(css.willChange) !== -1 ||
|
---|
56 | (isFirefox && css.willChange === 'filter') ||
|
---|
57 | (isFirefox && css.filter && css.filter !== 'none')
|
---|
58 | ) {
|
---|
59 | return currentNode;
|
---|
60 | } else {
|
---|
61 | currentNode = currentNode.parentNode;
|
---|
62 | }
|
---|
63 | }
|
---|
64 |
|
---|
65 | return null;
|
---|
66 | }
|
---|
67 |
|
---|
68 | // Gets the closest ancestor positioned element. Handles some edge cases,
|
---|
69 | // such as table ancestors and cross browser bugs.
|
---|
70 | export default function getOffsetParent(element: Element) {
|
---|
71 | const window = getWindow(element);
|
---|
72 |
|
---|
73 | let offsetParent = getTrueOffsetParent(element);
|
---|
74 |
|
---|
75 | while (
|
---|
76 | offsetParent &&
|
---|
77 | isTableElement(offsetParent) &&
|
---|
78 | getComputedStyle(offsetParent).position === 'static'
|
---|
79 | ) {
|
---|
80 | offsetParent = getTrueOffsetParent(offsetParent);
|
---|
81 | }
|
---|
82 |
|
---|
83 | if (
|
---|
84 | offsetParent &&
|
---|
85 | (getNodeName(offsetParent) === 'html' ||
|
---|
86 | (getNodeName(offsetParent) === 'body' &&
|
---|
87 | getComputedStyle(offsetParent).position === 'static'))
|
---|
88 | ) {
|
---|
89 | return window;
|
---|
90 | }
|
---|
91 |
|
---|
92 | return offsetParent || getContainingBlock(element) || window;
|
---|
93 | }
|
---|