package.lib.dom-utils.getClippingRect.js.flow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Tooltip and Popover Positioning Engine
// @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 {
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;
}