// @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; }