489 lines
15 KiB
JavaScript
489 lines
15 KiB
JavaScript
import { rectToClientRect, computePosition as computePosition$1 } from '@floating-ui/core';
|
|
export { arrow, autoPlacement, detectOverflow, flip, hide, inline, limitShift, offset, shift, size } from '@floating-ui/core';
|
|
|
|
function isWindow(value) {
|
|
return (value == null ? void 0 : value.toString()) === '[object Window]';
|
|
}
|
|
function getWindow(node) {
|
|
if (node == null) {
|
|
return window;
|
|
}
|
|
|
|
if (!isWindow(node)) {
|
|
const ownerDocument = node.ownerDocument;
|
|
return ownerDocument ? ownerDocument.defaultView || window : window;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function getComputedStyle$1(element) {
|
|
return getWindow(element).getComputedStyle(element);
|
|
}
|
|
|
|
function getNodeName(node) {
|
|
return isWindow(node) ? '' : node ? (node.nodeName || '').toLowerCase() : '';
|
|
}
|
|
|
|
function isHTMLElement(value) {
|
|
return value instanceof getWindow(value).HTMLElement;
|
|
}
|
|
function isElement(value) {
|
|
return value instanceof getWindow(value).Element;
|
|
}
|
|
function isNode(value) {
|
|
return value instanceof getWindow(value).Node;
|
|
}
|
|
function isShadowRoot(node) {
|
|
const OwnElement = getWindow(node).ShadowRoot;
|
|
return node instanceof OwnElement || node instanceof ShadowRoot;
|
|
}
|
|
function isScrollParent(element) {
|
|
// Firefox wants us to check `-x` and `-y` variations as well
|
|
const {
|
|
overflow,
|
|
overflowX,
|
|
overflowY
|
|
} = getComputedStyle$1(element);
|
|
return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);
|
|
}
|
|
function isTableElement(element) {
|
|
return ['table', 'td', 'th'].includes(getNodeName(element));
|
|
}
|
|
function isContainingBlock(element) {
|
|
// TODO: Try and use feature detection here instead
|
|
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');
|
|
const css = getComputedStyle$1(element); // This is non-exhaustive but covers the most common CSS properties that
|
|
// create a containing block.
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
|
|
|
|
return css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].includes(css.willChange) || isFirefox && css.willChange === 'filter' || isFirefox && (css.filter ? css.filter !== 'none' : false);
|
|
}
|
|
|
|
const min = Math.min;
|
|
const max = Math.max;
|
|
const round = Math.round;
|
|
|
|
function getBoundingClientRect(element, includeScale) {
|
|
if (includeScale === void 0) {
|
|
includeScale = false;
|
|
}
|
|
|
|
const clientRect = element.getBoundingClientRect();
|
|
let scaleX = 1;
|
|
let scaleY = 1;
|
|
|
|
if (includeScale && isHTMLElement(element)) {
|
|
scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;
|
|
scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;
|
|
}
|
|
|
|
return {
|
|
width: clientRect.width / scaleX,
|
|
height: clientRect.height / scaleY,
|
|
top: clientRect.top / scaleY,
|
|
right: clientRect.right / scaleX,
|
|
bottom: clientRect.bottom / scaleY,
|
|
left: clientRect.left / scaleX,
|
|
x: clientRect.left / scaleX,
|
|
y: clientRect.top / scaleY
|
|
};
|
|
}
|
|
|
|
function getDocumentElement(node) {
|
|
return ((isNode(node) ? node.ownerDocument : node.document) || window.document).documentElement;
|
|
}
|
|
|
|
function getNodeScroll(element) {
|
|
if (isWindow(element)) {
|
|
return {
|
|
scrollLeft: element.pageXOffset,
|
|
scrollTop: element.pageYOffset
|
|
};
|
|
}
|
|
|
|
return {
|
|
scrollLeft: element.scrollLeft,
|
|
scrollTop: element.scrollTop
|
|
};
|
|
}
|
|
|
|
function getWindowScrollBarX(element) {
|
|
// If <html> has a CSS width greater than the viewport, then this will be
|
|
// incorrect for RTL.
|
|
return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft;
|
|
}
|
|
|
|
function isScaled(element) {
|
|
const rect = getBoundingClientRect(element);
|
|
return round(rect.width) !== element.offsetWidth || round(rect.height) !== element.offsetHeight;
|
|
}
|
|
|
|
function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
|
|
const isOffsetParentAnElement = isHTMLElement(offsetParent);
|
|
const documentElement = getDocumentElement(offsetParent);
|
|
const rect = getBoundingClientRect(element, isOffsetParentAnElement && isScaled(offsetParent));
|
|
let scroll = {
|
|
scrollLeft: 0,
|
|
scrollTop: 0
|
|
};
|
|
const offsets = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') {
|
|
if (getNodeName(offsetParent) !== 'body' || isScrollParent(documentElement)) {
|
|
scroll = getNodeScroll(offsetParent);
|
|
}
|
|
|
|
if (isHTMLElement(offsetParent)) {
|
|
const offsetRect = getBoundingClientRect(offsetParent, true);
|
|
offsets.x = offsetRect.x + offsetParent.clientLeft;
|
|
offsets.y = offsetRect.y + offsetParent.clientTop;
|
|
} else if (documentElement) {
|
|
offsets.x = getWindowScrollBarX(documentElement);
|
|
}
|
|
}
|
|
|
|
return {
|
|
x: rect.left + scroll.scrollLeft - offsets.x,
|
|
y: rect.top + scroll.scrollTop - offsets.y,
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
}
|
|
|
|
function getParentNode(node) {
|
|
if (getNodeName(node) === 'html') {
|
|
return node;
|
|
}
|
|
|
|
return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle
|
|
// @ts-ignore
|
|
node.assignedSlot || // step into the shadow DOM of the parent of a slotted node
|
|
node.parentNode || ( // DOM Element detected
|
|
isShadowRoot(node) ? node.host : null) || // ShadowRoot detected
|
|
getDocumentElement(node) // fallback
|
|
|
|
);
|
|
}
|
|
|
|
function getTrueOffsetParent(element) {
|
|
if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
|
|
return null;
|
|
}
|
|
|
|
return element.offsetParent;
|
|
}
|
|
|
|
function getContainingBlock(element) {
|
|
let currentNode = getParentNode(element);
|
|
|
|
while (isHTMLElement(currentNode) && !['html', 'body'].includes(getNodeName(currentNode))) {
|
|
if (isContainingBlock(currentNode)) {
|
|
return currentNode;
|
|
} else {
|
|
currentNode = currentNode.parentNode;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
} // Gets the closest ancestor positioned element. Handles some edge cases,
|
|
// such as table ancestors and cross browser bugs.
|
|
|
|
|
|
function getOffsetParent(element) {
|
|
const window = getWindow(element);
|
|
let offsetParent = getTrueOffsetParent(element);
|
|
|
|
while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {
|
|
offsetParent = getTrueOffsetParent(offsetParent);
|
|
}
|
|
|
|
if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static' && !isContainingBlock(offsetParent))) {
|
|
return window;
|
|
}
|
|
|
|
return offsetParent || getContainingBlock(element) || window;
|
|
}
|
|
|
|
function getDimensions(element) {
|
|
return {
|
|
width: element.offsetWidth,
|
|
height: element.offsetHeight
|
|
};
|
|
}
|
|
|
|
function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
|
|
let {
|
|
rect,
|
|
offsetParent,
|
|
strategy
|
|
} = _ref;
|
|
const isOffsetParentAnElement = isHTMLElement(offsetParent);
|
|
const documentElement = getDocumentElement(offsetParent);
|
|
|
|
if (offsetParent === documentElement) {
|
|
return rect;
|
|
}
|
|
|
|
let scroll = {
|
|
scrollLeft: 0,
|
|
scrollTop: 0
|
|
};
|
|
const offsets = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') {
|
|
if (getNodeName(offsetParent) !== 'body' || isScrollParent(documentElement)) {
|
|
scroll = getNodeScroll(offsetParent);
|
|
}
|
|
|
|
if (isHTMLElement(offsetParent)) {
|
|
const offsetRect = getBoundingClientRect(offsetParent, true);
|
|
offsets.x = offsetRect.x + offsetParent.clientLeft;
|
|
offsets.y = offsetRect.y + offsetParent.clientTop;
|
|
} // This doesn't appear to be need to be negated.
|
|
// else if (documentElement) {
|
|
// offsets.x = getWindowScrollBarX(documentElement);
|
|
// }
|
|
|
|
}
|
|
|
|
return { ...rect,
|
|
x: rect.x - scroll.scrollLeft + offsets.x,
|
|
y: rect.y - scroll.scrollTop + offsets.y
|
|
};
|
|
}
|
|
|
|
function getViewportRect(element) {
|
|
const win = getWindow(element);
|
|
const html = getDocumentElement(element);
|
|
const visualViewport = win.visualViewport;
|
|
let width = html.clientWidth;
|
|
let height = html.clientHeight;
|
|
let x = 0;
|
|
let y = 0;
|
|
|
|
if (visualViewport) {
|
|
width = visualViewport.width;
|
|
height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently)
|
|
// In Chrome, it returns a value very close to 0 (+/-) but contains rounding
|
|
// errors due to floating point numbers, so we need to check precision.
|
|
// Safari returns a number <= 0, usually < -1 when pinch-zoomed
|
|
|
|
if (Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) < 0.01) {
|
|
x = visualViewport.offsetLeft;
|
|
y = visualViewport.offsetTop;
|
|
}
|
|
}
|
|
|
|
return {
|
|
width,
|
|
height,
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
|
|
// of the `<html>` and `<body>` rect bounds if horizontally scrollable
|
|
|
|
function getDocumentRect(element) {
|
|
var _element$ownerDocumen;
|
|
|
|
const html = getDocumentElement(element);
|
|
const scroll = getNodeScroll(element);
|
|
const body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;
|
|
const width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);
|
|
const height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);
|
|
let x = -scroll.scrollLeft + getWindowScrollBarX(element);
|
|
const y = -scroll.scrollTop;
|
|
|
|
if (getComputedStyle$1(body || html).direction === 'rtl') {
|
|
x += max(html.clientWidth, body ? body.clientWidth : 0) - width;
|
|
}
|
|
|
|
return {
|
|
width,
|
|
height,
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
|
|
function getScrollParent(node) {
|
|
if (['html', 'body', '#document'].includes(getNodeName(node))) {
|
|
// @ts-ignore assume body is always available
|
|
return node.ownerDocument.body;
|
|
}
|
|
|
|
if (isHTMLElement(node) && isScrollParent(node)) {
|
|
return node;
|
|
}
|
|
|
|
return getScrollParent(getParentNode(node));
|
|
}
|
|
|
|
function getScrollParents(node, list) {
|
|
var _node$ownerDocument;
|
|
|
|
if (list === void 0) {
|
|
list = [];
|
|
}
|
|
|
|
const scrollParent = getScrollParent(node);
|
|
const isBody = scrollParent === ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.body);
|
|
const win = getWindow(scrollParent);
|
|
const target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;
|
|
const updatedList = list.concat(target);
|
|
return isBody ? updatedList : // @ts-ignore: isBody tells us target will be an HTMLElement here
|
|
updatedList.concat(getScrollParents(getParentNode(target)));
|
|
}
|
|
|
|
function contains(parent, child) {
|
|
const rootNode = child.getRootNode == null ? void 0 : child.getRootNode(); // First, attempt with faster native method
|
|
|
|
if (parent.contains(child)) {
|
|
return true;
|
|
} // then fallback to custom implementation with Shadow DOM support
|
|
else if (rootNode && isShadowRoot(rootNode)) {
|
|
let next = child;
|
|
|
|
do {
|
|
// use `===` replace node.isSameNode()
|
|
if (next && parent === next) {
|
|
return true;
|
|
} // @ts-ignore: need a better way to handle this...
|
|
|
|
|
|
next = next.parentNode || next.host;
|
|
} while (next);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getInnerBoundingClientRect(element) {
|
|
const clientRect = getBoundingClientRect(element);
|
|
const top = clientRect.top + element.clientTop;
|
|
const left = clientRect.left + element.clientLeft;
|
|
return {
|
|
top,
|
|
left,
|
|
x: left,
|
|
y: top,
|
|
right: left + element.clientWidth,
|
|
bottom: top + element.clientHeight,
|
|
width: element.clientWidth,
|
|
height: element.clientHeight
|
|
};
|
|
}
|
|
|
|
function getClientRectFromClippingParent(element, clippingParent) {
|
|
if (clippingParent === 'viewport') {
|
|
return rectToClientRect(getViewportRect(element));
|
|
}
|
|
|
|
if (isElement(clippingParent)) {
|
|
return getInnerBoundingClientRect(clippingParent);
|
|
}
|
|
|
|
return 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) {
|
|
const clippingParents = getScrollParents(getParentNode(element));
|
|
const canEscapeClipping = ['absolute', 'fixed'].includes(getComputedStyle$1(element).position);
|
|
const clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;
|
|
|
|
if (!isElement(clipperElement)) {
|
|
return [];
|
|
} // @ts-ignore isElement check ensures we return Array<Element>
|
|
|
|
|
|
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
|
|
|
|
|
|
function getClippingClientRect(_ref) {
|
|
let {
|
|
element,
|
|
boundary,
|
|
rootBoundary
|
|
} = _ref;
|
|
const mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);
|
|
const clippingParents = [...mainClippingParents, rootBoundary];
|
|
const firstClippingParent = clippingParents[0];
|
|
const clippingRect = clippingParents.reduce((accRect, clippingParent) => {
|
|
const rect = getClientRectFromClippingParent(element, clippingParent);
|
|
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;
|
|
}, getClientRectFromClippingParent(element, firstClippingParent));
|
|
clippingRect.width = clippingRect.right - clippingRect.left;
|
|
clippingRect.height = clippingRect.bottom - clippingRect.top;
|
|
clippingRect.x = clippingRect.left;
|
|
clippingRect.y = clippingRect.top;
|
|
return clippingRect;
|
|
}
|
|
|
|
const platform = {
|
|
getElementRects: _ref => {
|
|
let {
|
|
reference,
|
|
floating,
|
|
strategy
|
|
} = _ref;
|
|
return {
|
|
reference: getRectRelativeToOffsetParent(reference, getOffsetParent(floating), strategy),
|
|
floating: { ...getDimensions(floating),
|
|
x: 0,
|
|
y: 0
|
|
}
|
|
};
|
|
},
|
|
convertOffsetParentRelativeRectToViewportRelativeRect: args => convertOffsetParentRelativeRectToViewportRelativeRect(args),
|
|
getOffsetParent: _ref2 => {
|
|
let {
|
|
element
|
|
} = _ref2;
|
|
return getOffsetParent(element);
|
|
},
|
|
isElement: value => isElement(value),
|
|
getDocumentElement: _ref3 => {
|
|
let {
|
|
element
|
|
} = _ref3;
|
|
return getDocumentElement(element);
|
|
},
|
|
getClippingClientRect: args => getClippingClientRect(args),
|
|
getDimensions: _ref4 => {
|
|
let {
|
|
element
|
|
} = _ref4;
|
|
return getDimensions(element);
|
|
},
|
|
getClientRects: _ref5 => {
|
|
let {
|
|
element
|
|
} = _ref5;
|
|
return element.getClientRects();
|
|
}
|
|
};
|
|
|
|
const computePosition = (reference, floating, options) => computePosition$1(reference, floating, {
|
|
platform,
|
|
...options
|
|
});
|
|
|
|
export { computePosition, getScrollParents };
|