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 getWindow(node) {
|
var _node$ownerDocument;
|
return ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
|
}
|
|
function getComputedStyle$1(element) {
|
return getWindow(element).getComputedStyle(element);
|
}
|
|
function isNode(value) {
|
return value instanceof getWindow(value).Node;
|
}
|
function getNodeName(node) {
|
return isNode(node) ? (node.nodeName || '').toLowerCase() : '';
|
}
|
|
function isHTMLElement(value) {
|
return value instanceof getWindow(value).HTMLElement;
|
}
|
function isElement(value) {
|
return value instanceof getWindow(value).Element;
|
}
|
function isShadowRoot(node) {
|
// Browsers without `ShadowRoot` support.
|
if (typeof ShadowRoot === 'undefined') {
|
return false;
|
}
|
const OwnElement = getWindow(node).ShadowRoot;
|
return node instanceof OwnElement || node instanceof ShadowRoot;
|
}
|
function isOverflowElement(element) {
|
const {
|
overflow,
|
overflowX,
|
overflowY,
|
display
|
} = getComputedStyle$1(element);
|
return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display);
|
}
|
function isTableElement(element) {
|
return ['table', 'td', 'th'].includes(getNodeName(element));
|
}
|
function isContainingBlock(element) {
|
const safari = isSafari();
|
const css = getComputedStyle$1(element);
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
|
return css.transform !== 'none' || css.perspective !== 'none' || !safari && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !safari && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value));
|
}
|
function isSafari() {
|
if (typeof CSS === 'undefined' || !CSS.supports) return false;
|
return CSS.supports('-webkit-backdrop-filter', 'none');
|
}
|
function isLastTraversableNode(node) {
|
return ['html', 'body', '#document'].includes(getNodeName(node));
|
}
|
|
const min = Math.min;
|
const max = Math.max;
|
const round = Math.round;
|
|
function getCssDimensions(element) {
|
const css = getComputedStyle$1(element);
|
// In testing environments, the `width` and `height` properties are empty
|
// strings for SVG elements, returning NaN. Fallback to `0` in this case.
|
let width = parseFloat(css.width) || 0;
|
let height = parseFloat(css.height) || 0;
|
const hasOffset = isHTMLElement(element);
|
const offsetWidth = hasOffset ? element.offsetWidth : width;
|
const offsetHeight = hasOffset ? element.offsetHeight : height;
|
const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
|
if (shouldFallback) {
|
width = offsetWidth;
|
height = offsetHeight;
|
}
|
return {
|
width,
|
height,
|
fallback: shouldFallback
|
};
|
}
|
|
function unwrapElement(element) {
|
return !isElement(element) ? element.contextElement : element;
|
}
|
|
const FALLBACK_SCALE = {
|
x: 1,
|
y: 1
|
};
|
function getScale(element) {
|
const domElement = unwrapElement(element);
|
if (!isHTMLElement(domElement)) {
|
return FALLBACK_SCALE;
|
}
|
const rect = domElement.getBoundingClientRect();
|
const {
|
width,
|
height,
|
fallback
|
} = getCssDimensions(domElement);
|
let x = (fallback ? round(rect.width) : rect.width) / width;
|
let y = (fallback ? round(rect.height) : rect.height) / height;
|
|
// 0, NaN, or Infinity should always fallback to 1.
|
|
if (!x || !Number.isFinite(x)) {
|
x = 1;
|
}
|
if (!y || !Number.isFinite(y)) {
|
y = 1;
|
}
|
return {
|
x,
|
y
|
};
|
}
|
|
const noOffsets = {
|
x: 0,
|
y: 0
|
};
|
function getVisualOffsets(element, isFixed, floatingOffsetParent) {
|
var _win$visualViewport, _win$visualViewport2;
|
if (isFixed === void 0) {
|
isFixed = true;
|
}
|
if (!isSafari()) {
|
return noOffsets;
|
}
|
const win = element ? getWindow(element) : window;
|
if (!floatingOffsetParent || isFixed && floatingOffsetParent !== win) {
|
return noOffsets;
|
}
|
return {
|
x: ((_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) || 0,
|
y: ((_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) || 0
|
};
|
}
|
|
function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
|
if (includeScale === void 0) {
|
includeScale = false;
|
}
|
if (isFixedStrategy === void 0) {
|
isFixedStrategy = false;
|
}
|
const clientRect = element.getBoundingClientRect();
|
const domElement = unwrapElement(element);
|
let scale = FALLBACK_SCALE;
|
if (includeScale) {
|
if (offsetParent) {
|
if (isElement(offsetParent)) {
|
scale = getScale(offsetParent);
|
}
|
} else {
|
scale = getScale(element);
|
}
|
}
|
const visualOffsets = getVisualOffsets(domElement, isFixedStrategy, offsetParent);
|
let x = (clientRect.left + visualOffsets.x) / scale.x;
|
let y = (clientRect.top + visualOffsets.y) / scale.y;
|
let width = clientRect.width / scale.x;
|
let height = clientRect.height / scale.y;
|
if (domElement) {
|
const win = getWindow(domElement);
|
const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent;
|
let currentIFrame = win.frameElement;
|
while (currentIFrame && offsetParent && offsetWin !== win) {
|
const iframeScale = getScale(currentIFrame);
|
const iframeRect = currentIFrame.getBoundingClientRect();
|
const css = getComputedStyle(currentIFrame);
|
iframeRect.x += (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
|
iframeRect.y += (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
|
x *= iframeScale.x;
|
y *= iframeScale.y;
|
width *= iframeScale.x;
|
height *= iframeScale.y;
|
x += iframeRect.x;
|
y += iframeRect.y;
|
currentIFrame = getWindow(currentIFrame).frameElement;
|
}
|
}
|
return rectToClientRect({
|
width,
|
height,
|
x,
|
y
|
});
|
}
|
|
function getDocumentElement(node) {
|
return ((isNode(node) ? node.ownerDocument : node.document) || window.document).documentElement;
|
}
|
|
function getNodeScroll(element) {
|
if (isElement(element)) {
|
return {
|
scrollLeft: element.scrollLeft,
|
scrollTop: element.scrollTop
|
};
|
}
|
return {
|
scrollLeft: element.pageXOffset,
|
scrollTop: element.pageYOffset
|
};
|
}
|
|
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
|
};
|
let scale = {
|
x: 1,
|
y: 1
|
};
|
const offsets = {
|
x: 0,
|
y: 0
|
};
|
if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') {
|
if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
|
scroll = getNodeScroll(offsetParent);
|
}
|
if (isHTMLElement(offsetParent)) {
|
const offsetRect = getBoundingClientRect(offsetParent);
|
scale = getScale(offsetParent);
|
offsets.x = offsetRect.x + offsetParent.clientLeft;
|
offsets.y = offsetRect.y + offsetParent.clientTop;
|
}
|
}
|
return {
|
width: rect.width * scale.x,
|
height: rect.height * scale.y,
|
x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x,
|
y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y
|
};
|
}
|
|
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;
|
}
|
|
// Gets the entire size of the scrollable document area, even extending outside
|
// of the `<html>` and `<body>` rect bounds if horizontally scrollable.
|
function getDocumentRect(element) {
|
const html = getDocumentElement(element);
|
const scroll = getNodeScroll(element);
|
const body = element.ownerDocument.body;
|
const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth);
|
const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
|
let x = -scroll.scrollLeft + getWindowScrollBarX(element);
|
const y = -scroll.scrollTop;
|
if (getComputedStyle$1(body).direction === 'rtl') {
|
x += max(html.clientWidth, body.clientWidth) - width;
|
}
|
return {
|
width,
|
height,
|
x,
|
y
|
};
|
}
|
|
function getParentNode(node) {
|
if (getNodeName(node) === 'html') {
|
return node;
|
}
|
const result =
|
// Step into the shadow DOM of the parent of a slotted node.
|
node.assignedSlot ||
|
// DOM Element detected.
|
node.parentNode ||
|
// ShadowRoot detected.
|
isShadowRoot(node) && node.host ||
|
// Fallback.
|
getDocumentElement(node);
|
return isShadowRoot(result) ? result.host : result;
|
}
|
|
function getNearestOverflowAncestor(node) {
|
const parentNode = getParentNode(node);
|
if (isLastTraversableNode(parentNode)) {
|
// `getParentNode` will never return a `Document` due to the fallback
|
// check, so it's either the <html> or <body> element.
|
return parentNode.ownerDocument.body;
|
}
|
if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
|
return parentNode;
|
}
|
return getNearestOverflowAncestor(parentNode);
|
}
|
|
function getOverflowAncestors(node, list) {
|
var _node$ownerDocument;
|
if (list === void 0) {
|
list = [];
|
}
|
const scrollableAncestor = getNearestOverflowAncestor(node);
|
const isBody = scrollableAncestor === ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.body);
|
const win = getWindow(scrollableAncestor);
|
if (isBody) {
|
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []);
|
}
|
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor));
|
}
|
|
function getViewportRect(element, strategy) {
|
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;
|
const visualViewportBased = isSafari();
|
if (!visualViewportBased || visualViewportBased && strategy === 'fixed') {
|
x = visualViewport.offsetLeft;
|
y = visualViewport.offsetTop;
|
}
|
}
|
return {
|
width,
|
height,
|
x,
|
y
|
};
|
}
|
|
// Returns the inner client rect, subtracting scrollbars if present.
|
function getInnerBoundingClientRect(element, strategy) {
|
const clientRect = getBoundingClientRect(element, true, strategy === 'fixed');
|
const top = clientRect.top + element.clientTop;
|
const left = clientRect.left + element.clientLeft;
|
const scale = isHTMLElement(element) ? getScale(element) : {
|
x: 1,
|
y: 1
|
};
|
const width = element.clientWidth * scale.x;
|
const height = element.clientHeight * scale.y;
|
const x = left * scale.x;
|
const y = top * scale.y;
|
return {
|
width,
|
height,
|
x,
|
y
|
};
|
}
|
function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {
|
let rect;
|
if (clippingAncestor === 'viewport') {
|
rect = getViewportRect(element, strategy);
|
} else if (clippingAncestor === 'document') {
|
rect = getDocumentRect(getDocumentElement(element));
|
} else if (isElement(clippingAncestor)) {
|
rect = getInnerBoundingClientRect(clippingAncestor, strategy);
|
} else {
|
const visualOffsets = getVisualOffsets(element);
|
rect = {
|
...clippingAncestor,
|
x: clippingAncestor.x - visualOffsets.x,
|
y: clippingAncestor.y - visualOffsets.y
|
};
|
}
|
return rectToClientRect(rect);
|
}
|
function hasFixedPositionAncestor(element, stopNode) {
|
const parentNode = getParentNode(element);
|
if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
|
return false;
|
}
|
return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
|
}
|
|
// A "clipping ancestor" is an `overflow` element with the characteristic of
|
// clipping (or hiding) child elements. This returns all clipping ancestors
|
// of the given element up the tree.
|
function getClippingElementAncestors(element, cache) {
|
const cachedResult = cache.get(element);
|
if (cachedResult) {
|
return cachedResult;
|
}
|
let result = getOverflowAncestors(element).filter(el => isElement(el) && getNodeName(el) !== 'body');
|
let currentContainingBlockComputedStyle = null;
|
const elementIsFixed = getComputedStyle$1(element).position === 'fixed';
|
let currentNode = elementIsFixed ? getParentNode(element) : element;
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
|
while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
|
const computedStyle = getComputedStyle$1(currentNode);
|
const currentNodeIsContaining = isContainingBlock(currentNode);
|
if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
|
currentContainingBlockComputedStyle = null;
|
}
|
const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
|
if (shouldDropCurrentNode) {
|
// Drop non-containing blocks.
|
result = result.filter(ancestor => ancestor !== currentNode);
|
} else {
|
// Record last containing block for next iteration.
|
currentContainingBlockComputedStyle = computedStyle;
|
}
|
currentNode = getParentNode(currentNode);
|
}
|
cache.set(element, result);
|
return result;
|
}
|
|
// Gets the maximum area that the element is visible in due to any number of
|
// clipping ancestors.
|
function getClippingRect(_ref) {
|
let {
|
element,
|
boundary,
|
rootBoundary,
|
strategy
|
} = _ref;
|
const elementClippingAncestors = boundary === 'clippingAncestors' ? getClippingElementAncestors(element, this._c) : [].concat(boundary);
|
const clippingAncestors = [...elementClippingAncestors, rootBoundary];
|
const firstClippingAncestor = clippingAncestors[0];
|
const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
|
const rect = getClientRectFromClippingAncestor(element, clippingAncestor, 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;
|
}, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
|
return {
|
width: clippingRect.right - clippingRect.left,
|
height: clippingRect.bottom - clippingRect.top,
|
x: clippingRect.left,
|
y: clippingRect.top
|
};
|
}
|
|
function getDimensions(element) {
|
return getCssDimensions(element);
|
}
|
|
function getTrueOffsetParent(element, polyfill) {
|
if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') {
|
return null;
|
}
|
if (polyfill) {
|
return polyfill(element);
|
}
|
return element.offsetParent;
|
}
|
function getContainingBlock(element) {
|
let currentNode = getParentNode(element);
|
while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
|
if (isContainingBlock(currentNode)) {
|
return currentNode;
|
} else {
|
currentNode = getParentNode(currentNode);
|
}
|
}
|
return null;
|
}
|
|
// Gets the closest ancestor positioned element. Handles some edge cases,
|
// such as table ancestors and cross browser bugs.
|
function getOffsetParent(element, polyfill) {
|
const window = getWindow(element);
|
if (!isHTMLElement(element)) {
|
return window;
|
}
|
let offsetParent = getTrueOffsetParent(element, polyfill);
|
while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === 'static') {
|
offsetParent = getTrueOffsetParent(offsetParent, polyfill);
|
}
|
if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle$1(offsetParent).position === 'static' && !isContainingBlock(offsetParent))) {
|
return window;
|
}
|
return offsetParent || getContainingBlock(element) || window;
|
}
|
|
function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
|
const isOffsetParentAnElement = isHTMLElement(offsetParent);
|
const documentElement = getDocumentElement(offsetParent);
|
const isFixed = strategy === 'fixed';
|
const rect = getBoundingClientRect(element, true, isFixed, offsetParent);
|
let scroll = {
|
scrollLeft: 0,
|
scrollTop: 0
|
};
|
const offsets = {
|
x: 0,
|
y: 0
|
};
|
if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
|
if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
|
scroll = getNodeScroll(offsetParent);
|
}
|
if (isHTMLElement(offsetParent)) {
|
const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent);
|
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
|
};
|
}
|
|
const platform = {
|
getClippingRect,
|
convertOffsetParentRelativeRectToViewportRelativeRect,
|
isElement,
|
getDimensions,
|
getOffsetParent,
|
getDocumentElement,
|
getScale,
|
async getElementRects(_ref) {
|
let {
|
reference,
|
floating,
|
strategy
|
} = _ref;
|
const getOffsetParentFn = this.getOffsetParent || getOffsetParent;
|
const getDimensionsFn = this.getDimensions;
|
return {
|
reference: getRectRelativeToOffsetParent(reference, await getOffsetParentFn(floating), strategy),
|
floating: {
|
x: 0,
|
y: 0,
|
...(await getDimensionsFn(floating))
|
}
|
};
|
},
|
getClientRects: element => Array.from(element.getClientRects()),
|
isRTL: element => getComputedStyle$1(element).direction === 'rtl'
|
};
|
|
/**
|
* Automatically updates the position of the floating element when necessary.
|
* Should only be called when the floating element is mounted on the DOM or
|
* visible on the screen.
|
* @returns cleanup function that should be invoked when the floating element is
|
* removed from the DOM or hidden from the screen.
|
* @see https://floating-ui.com/docs/autoUpdate
|
*/
|
function autoUpdate(reference, floating, update, options) {
|
if (options === void 0) {
|
options = {};
|
}
|
const {
|
ancestorScroll = true,
|
ancestorResize = true,
|
elementResize = true,
|
animationFrame = false
|
} = options;
|
const ancestors = ancestorScroll || ancestorResize ? [...(isElement(reference) ? getOverflowAncestors(reference) : reference.contextElement ? getOverflowAncestors(reference.contextElement) : []), ...getOverflowAncestors(floating)] : [];
|
ancestors.forEach(ancestor => {
|
// ignores Window, checks for [object VisualViewport]
|
const isVisualViewport = !isElement(ancestor) && ancestor.toString().includes('V');
|
if (ancestorScroll && (animationFrame ? isVisualViewport : true)) {
|
ancestor.addEventListener('scroll', update, {
|
passive: true
|
});
|
}
|
ancestorResize && ancestor.addEventListener('resize', update);
|
});
|
let observer = null;
|
if (elementResize) {
|
observer = new ResizeObserver(() => {
|
update();
|
});
|
isElement(reference) && !animationFrame && observer.observe(reference);
|
if (!isElement(reference) && reference.contextElement && !animationFrame) {
|
observer.observe(reference.contextElement);
|
}
|
observer.observe(floating);
|
}
|
let frameId;
|
let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null;
|
if (animationFrame) {
|
frameLoop();
|
}
|
function frameLoop() {
|
const nextRefRect = getBoundingClientRect(reference);
|
if (prevRefRect && (nextRefRect.x !== prevRefRect.x || nextRefRect.y !== prevRefRect.y || nextRefRect.width !== prevRefRect.width || nextRefRect.height !== prevRefRect.height)) {
|
update();
|
}
|
prevRefRect = nextRefRect;
|
frameId = requestAnimationFrame(frameLoop);
|
}
|
update();
|
return () => {
|
var _observer;
|
ancestors.forEach(ancestor => {
|
ancestorScroll && ancestor.removeEventListener('scroll', update);
|
ancestorResize && ancestor.removeEventListener('resize', update);
|
});
|
(_observer = observer) == null ? void 0 : _observer.disconnect();
|
observer = null;
|
if (animationFrame) {
|
cancelAnimationFrame(frameId);
|
}
|
};
|
}
|
|
/**
|
* Computes the `x` and `y` coordinates that will place the floating element
|
* next to a reference element when it is given a certain CSS positioning
|
* strategy.
|
*/
|
const computePosition = (reference, floating, options) => {
|
// This caches the expensive `getClippingElementAncestors` function so that
|
// multiple lifecycle resets re-use the same result. It only lives for a
|
// single call. If other functions become expensive, we can add them as well.
|
const cache = new Map();
|
const mergedOptions = {
|
platform,
|
...options
|
};
|
const platformWithCache = {
|
...mergedOptions.platform,
|
_c: cache
|
};
|
return computePosition$1(reference, floating, {
|
...mergedOptions,
|
platform: platformWithCache
|
});
|
};
|
|
export { autoUpdate, computePosition, getOverflowAncestors, platform };
|