1 | // @flow
|
---|
2 | import type { ClientRectObject, PositioningStrategy } from '../types';
|
---|
3 | import type { Boundary, RootBoundary } from '../enums';
|
---|
4 | import { viewport } from '../enums';
|
---|
5 | import getViewportRect from './getViewportRect';
|
---|
6 | import getDocumentRect from './getDocumentRect';
|
---|
7 | import listScrollParents from './listScrollParents';
|
---|
8 | import getOffsetParent from './getOffsetParent';
|
---|
9 | import getDocumentElement from './getDocumentElement';
|
---|
10 | import getComputedStyle from './getComputedStyle';
|
---|
11 | import { isElement, isHTMLElement } from './instanceOf';
|
---|
12 | import getBoundingClientRect from './getBoundingClientRect';
|
---|
13 | import getParentNode from './getParentNode';
|
---|
14 | import contains from './contains';
|
---|
15 | import getNodeName from './getNodeName';
|
---|
16 | import rectToClientRect from '../utils/rectToClientRect';
|
---|
17 | import { max, min } from '../utils/math';
|
---|
18 |
|
---|
19 | function getInnerBoundingClientRect(
|
---|
20 | element: Element,
|
---|
21 | strategy: PositioningStrategy
|
---|
22 | ) {
|
---|
23 | const rect = getBoundingClientRect(element, false, strategy === 'fixed');
|
---|
24 |
|
---|
25 | rect.top = rect.top + element.clientTop;
|
---|
26 | rect.left = rect.left + element.clientLeft;
|
---|
27 | rect.bottom = rect.top + element.clientHeight;
|
---|
28 | rect.right = rect.left + element.clientWidth;
|
---|
29 | rect.width = element.clientWidth;
|
---|
30 | rect.height = element.clientHeight;
|
---|
31 | rect.x = rect.left;
|
---|
32 | rect.y = rect.top;
|
---|
33 |
|
---|
34 | return rect;
|
---|
35 | }
|
---|
36 |
|
---|
37 | function getClientRectFromMixedType(
|
---|
38 | element: Element,
|
---|
39 | clippingParent: Element | RootBoundary,
|
---|
40 | strategy: PositioningStrategy
|
---|
41 | ): ClientRectObject {
|
---|
42 | return clippingParent === viewport
|
---|
43 | ? rectToClientRect(getViewportRect(element, strategy))
|
---|
44 | : isElement(clippingParent)
|
---|
45 | ? getInnerBoundingClientRect(clippingParent, strategy)
|
---|
46 | : rectToClientRect(getDocumentRect(getDocumentElement(element)));
|
---|
47 | }
|
---|
48 |
|
---|
49 | // A "clipping parent" is an overflowable container with the characteristic of
|
---|
50 | // clipping (or hiding) overflowing elements with a position different from
|
---|
51 | // `initial`
|
---|
52 | function getClippingParents(element: Element): Array<Element> {
|
---|
53 | const clippingParents = listScrollParents(getParentNode(element));
|
---|
54 | const canEscapeClipping =
|
---|
55 | ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;
|
---|
56 | const clipperElement =
|
---|
57 | canEscapeClipping && isHTMLElement(element)
|
---|
58 | ? getOffsetParent(element)
|
---|
59 | : element;
|
---|
60 |
|
---|
61 | if (!isElement(clipperElement)) {
|
---|
62 | return [];
|
---|
63 | }
|
---|
64 |
|
---|
65 | // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414
|
---|
66 | return clippingParents.filter(
|
---|
67 | (clippingParent) =>
|
---|
68 | isElement(clippingParent) &&
|
---|
69 | contains(clippingParent, clipperElement) &&
|
---|
70 | getNodeName(clippingParent) !== 'body'
|
---|
71 | );
|
---|
72 | }
|
---|
73 |
|
---|
74 | // Gets the maximum area that the element is visible in due to any number of
|
---|
75 | // clipping parents
|
---|
76 | export default function getClippingRect(
|
---|
77 | element: Element,
|
---|
78 | boundary: Boundary,
|
---|
79 | rootBoundary: RootBoundary,
|
---|
80 | strategy: PositioningStrategy
|
---|
81 | ): ClientRectObject {
|
---|
82 | const mainClippingParents =
|
---|
83 | boundary === 'clippingParents'
|
---|
84 | ? getClippingParents(element)
|
---|
85 | : [].concat(boundary);
|
---|
86 | const clippingParents = [...mainClippingParents, rootBoundary];
|
---|
87 | const firstClippingParent = clippingParents[0];
|
---|
88 |
|
---|
89 | const clippingRect = clippingParents.reduce((accRect, clippingParent) => {
|
---|
90 | const rect = getClientRectFromMixedType(element, clippingParent, strategy);
|
---|
91 |
|
---|
92 | accRect.top = max(rect.top, accRect.top);
|
---|
93 | accRect.right = min(rect.right, accRect.right);
|
---|
94 | accRect.bottom = min(rect.bottom, accRect.bottom);
|
---|
95 | accRect.left = max(rect.left, accRect.left);
|
---|
96 |
|
---|
97 | return accRect;
|
---|
98 | }, getClientRectFromMixedType(element, firstClippingParent, strategy));
|
---|
99 |
|
---|
100 | clippingRect.width = clippingRect.right - clippingRect.left;
|
---|
101 | clippingRect.height = clippingRect.bottom - clippingRect.top;
|
---|
102 | clippingRect.x = clippingRect.left;
|
---|
103 | clippingRect.y = clippingRect.top;
|
---|
104 |
|
---|
105 | return clippingRect;
|
---|
106 | }
|
---|