/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/export interface Rect {
bottom: number;
height: number;
left: number;
right: number;
top: number;
width: number;
}// Get the window object for the document that a node belongs to,// or return null if it cannot be found (node not attached to DOM,// etc).export function getOwnerWindow(node: HTMLElement): typeof window | null {
if (!node.ownerDocument) {
return null;
}return node.ownerDocument.defaultView;
}// Get the iframe containing a node, or return null if it cannot// be found (node not within iframe, etc).export function getOwnerIframe(node: HTMLElement): HTMLElement | null {
const nodeWindow = getOwnerWindow(node);
if (nodeWindow) {
return nodeWindow.frameElement;
}return null;
}// Get a bounding client rect for a node, with an// offset added to compensate for its border.export function getBoundingClientRectWithBorderOffset(node: HTMLElement): Rect {
const dimensions = getElementDimensions(node);
return mergeRectOffsets([
node.getBoundingClientRect(),
{top: dimensions.borderTop,
left: dimensions.borderLeft,
bottom: dimensions.borderBottom,
right: dimensions.borderRight,
// This width and height won't get used by mergeRectOffsets (since this
// is not the first rect in the array), but we set them so that this
// object type checks as a ClientRect.
width: 0,
height: 0,
},]);}// Add together the top, left, bottom, and right properties of// each ClientRect, but keep the width and height of the first one.export function mergeRectOffsets(rects: Array<Rect>): Rect {
return rects.reduce((previousRect, rect) => {
if (previousRect == null) {
return rect;
}return {top: previousRect.top + rect.top,
left: previousRect.left + rect.left,
width: previousRect.width,
height: previousRect.height,
bottom: previousRect.bottom + rect.bottom,
right: previousRect.right + rect.right,
};});}// Calculate a boundingClientRect for a node relative to boundaryWindow,// taking into account any offsets caused by intermediate iframes.export function getNestedBoundingClientRect(
node: HTMLElement,
boundaryWindow: typeof window,
): Rect {
const ownerIframe = getOwnerIframe(node);
if (ownerIframe && ownerIframe !== boundaryWindow) {
const rects: Array<Rect | ClientRect> = [node.getBoundingClientRect()];
let currentIframe: null | HTMLElement = ownerIframe;
let onlyOneMore = false;
while (currentIframe) {
const rect = getBoundingClientRectWithBorderOffset(currentIframe);
rects.push(rect);
currentIframe = getOwnerIframe(currentIframe);
if (onlyOneMore) {
break;
}// We don't want to calculate iframe offsets upwards beyond
// the iframe containing the boundaryWindow, but we
// need to calculate the offset relative to the boundaryWindow.
if (currentIframe && getOwnerWindow(currentIframe) === boundaryWindow) {
onlyOneMore = true;
}}return mergeRectOffsets(rects);
} else {
return node.getBoundingClientRect();
}}export function getElementDimensions(domElement: Element): {
borderBottom: number,
borderLeft: number,
borderRight: number,
borderTop: number,
marginBottom: number,
marginLeft: number,
marginRight: number,
marginTop: number,
paddingBottom: number,
paddingLeft: number,
paddingRight: number,
paddingTop: number,
} {const calculatedStyle = window.getComputedStyle(domElement);
return {
borderLeft: parseInt(calculatedStyle.borderLeftWidth, 10),
borderRight: parseInt(calculatedStyle.borderRightWidth, 10),
borderTop: parseInt(calculatedStyle.borderTopWidth, 10),
borderBottom: parseInt(calculatedStyle.borderBottomWidth, 10),
marginLeft: parseInt(calculatedStyle.marginLeft, 10),
marginRight: parseInt(calculatedStyle.marginRight, 10),
marginTop: parseInt(calculatedStyle.marginTop, 10),
marginBottom: parseInt(calculatedStyle.marginBottom, 10),
paddingLeft: parseInt(calculatedStyle.paddingLeft, 10),
paddingRight: parseInt(calculatedStyle.paddingRight, 10),
paddingTop: parseInt(calculatedStyle.paddingTop, 10),
paddingBottom: parseInt(calculatedStyle.paddingBottom, 10),
};}