You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

106 lines
3.7 KiB

// @flow
import type { ClientRectObject, PositioningStrategy } from '../types';
import type { Boundary, RootBoundary } from '../enums';
import { viewport } from '../enums';
import getViewportRect from './getViewportRect';
import getDocumentRect from './getDocumentRect';
import listScrollParents from './listScrollParents';
import getOffsetParent from './getOffsetParent';
import getDocumentElement from './getDocumentElement';
import getComputedStyle from './getComputedStyle';
import { isElement, isHTMLElement } from './instanceOf';
import getBoundingClientRect from './getBoundingClientRect';
import getParentNode from './getParentNode';
import contains from './contains';
import getNodeName from './getNodeName';
import rectToClientRect from '../utils/rectToClientRect';
import { max, min } from '../utils/math';
function getInnerBoundingClientRect(
element: Element,
strategy: PositioningStrategy
) {
const rect = getBoundingClientRect(element, false, strategy === 'fixed');
rect.top = rect.top + element.clientTop;
rect.left = rect.left + element.clientLeft;
rect.bottom = rect.top + element.clientHeight;
rect.right = rect.left + element.clientWidth;
rect.width = element.clientWidth;
rect.height = element.clientHeight;
rect.x = rect.left;
rect.y = rect.top;
return rect;
}
function getClientRectFromMixedType(
element: Element,
clippingParent: Element | RootBoundary,
strategy: PositioningStrategy
): ClientRectObject {
return clippingParent === viewport
? rectToClientRect(getViewportRect(element, strategy))
: isElement(clippingParent)
? getInnerBoundingClientRect(clippingParent, strategy)
: rectToClientRect(getDocumentRect(getDocumentElement(element)));
}
// A "clipping parent" is an overflowable container with the characteristic of
// clipping (or hiding) overflowing elements with a position different from
// `initial`
function getClippingParents(element: Element): Array<Element> {
const clippingParents = listScrollParents(getParentNode(element));
const canEscapeClipping =
['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;
const clipperElement =
canEscapeClipping && isHTMLElement(element)
? getOffsetParent(element)
: element;
if (!isElement(clipperElement)) {
return [];
}
// $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414
return clippingParents.filter(
(clippingParent) =>
isElement(clippingParent) &&
contains(clippingParent, clipperElement) &&
getNodeName(clippingParent) !== 'body'
);
}
// Gets the maximum area that the element is visible in due to any number of
// clipping parents
export default function getClippingRect(
element: Element,
boundary: Boundary,
rootBoundary: RootBoundary,
strategy: PositioningStrategy
): ClientRectObject {
const mainClippingParents =
boundary === 'clippingParents'
? getClippingParents(element)
: [].concat(boundary);
const clippingParents = [...mainClippingParents, rootBoundary];
const firstClippingParent = clippingParents[0];
const clippingRect = clippingParents.reduce((accRect, clippingParent) => {
const rect = getClientRectFromMixedType(element, clippingParent, strategy);
accRect.top = max(rect.top, accRect.top);
accRect.right = min(rect.right, accRect.right);
accRect.bottom = min(rect.bottom, accRect.bottom);
accRect.left = max(rect.left, accRect.left);
return accRect;
}, getClientRectFromMixedType(element, firstClippingParent, strategy));
clippingRect.width = clippingRect.right - clippingRect.left;
clippingRect.height = clippingRect.bottom - clippingRect.top;
clippingRect.x = clippingRect.left;
clippingRect.y = clippingRect.top;
return clippingRect;
}