You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

643 lines
21 KiB

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 getNodeName(node) {
return isNode(node) ? (node.nodeName || '').toLowerCase() : '';
}
let uaString;
function getUAString() {
if (uaString) {
return uaString;
}
const uaData = navigator.userAgentData;
if (uaData && Array.isArray(uaData.brands)) {
uaString = uaData.brands.map(item => item.brand + "/" + item.version).join(' ');
return uaString;
}
return navigator.userAgent;
}
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) {
// 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) {
// TODO: Try and use feature detection here instead
const isFirefox = /firefox/i.test(getUAString());
const css = getComputedStyle$1(element);
const backdropFilter = css.backdropFilter || css.WebkitBackdropFilter;
// 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' || (backdropFilter ? backdropFilter !== 'none' : false) || isFirefox && css.willChange === 'filter' || isFirefox && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective'].some(value => css.willChange.includes(value)) || ['paint', 'layout', 'strict', 'content'].some(
// TS 4.1 compat
value => {
const contain = css.contain;
return contain != null ? contain.includes(value) : false;
});
}
function isLayoutViewport() {
// Not Safari
return !/^((?!chrome|android).)*safari/i.test(getUAString());
// Feature detection for this fails in various ways
// • Always-visible scrollbar or not
// • Width of <html>, etc.
// const vV = win.visualViewport;
// return vV ? Math.abs(win.innerWidth / vV.scale - vV.width) < 0.5 : true;
}
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);
let width = parseFloat(css.width);
let height = parseFloat(css.height);
const offsetWidth = element.offsetWidth;
const offsetHeight = element.offsetHeight;
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
};
}
function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
var _win$visualViewport, _win$visualViewport2;
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 win = domElement ? getWindow(domElement) : window;
const addVisualOffsets = !isLayoutViewport() && isFixedStrategy;
let x = (clientRect.left + (addVisualOffsets ? ((_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) || 0 : 0)) / scale.x;
let y = (clientRect.top + (addVisualOffsets ? ((_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) || 0 : 0)) / 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 {
width,
height,
top: y,
right: x + width,
bottom: y + height,
left: x,
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 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 getRectRelativeToOffsetParent(element, offsetParent, strategy) {
const isOffsetParentAnElement = isHTMLElement(offsetParent);
const documentElement = getDocumentElement(offsetParent);
const rect = getBoundingClientRect(element, true, strategy === 'fixed', offsetParent);
let scroll = {
scrollLeft: 0,
scrollTop: 0
};
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, 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;
}
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 : null) ||
// Fallback
getDocumentElement(node);
return isShadowRoot(result) ? result.host : result;
}
function getTrueOffsetParent(element) {
if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') {
return null;
}
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) {
const window = getWindow(element);
let offsetParent = getTrueOffsetParent(element);
while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === 'static') {
offsetParent = getTrueOffsetParent(offsetParent);
}
if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle$1(offsetParent).position === 'static' && !isContainingBlock(offsetParent))) {
return window;
}
return offsetParent || getContainingBlock(element) || window;
}
function getDimensions(element) {
return getCssDimensions(element);
}
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;
}
// This doesn't appear to need to be negated.
// else if (documentElement) {
// offsets.x = getWindowScrollBarX(documentElement);
// }
}
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 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 layoutViewport = isLayoutViewport();
if (layoutViewport || !layoutViewport && strategy === 'fixed') {
x = visualViewport.offsetLeft;
y = visualViewport.offsetTop;
}
}
return {
width,
height,
x,
y
};
}
// 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) {
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 getNearestOverflowAncestor(node) {
const parentNode = getParentNode(node);
if (isLastTraversableNode(parentNode)) {
// @ts-ignore assume body is always available
return node.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));
}
// 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 {
top: y,
left: x,
right: x + width,
bottom: y + height,
x,
y,
width,
height
};
}
function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {
if (clippingAncestor === 'viewport') {
return rectToClientRect(getViewportRect(element, strategy));
}
if (isElement(clippingAncestor)) {
return getInnerBoundingClientRect(clippingAncestor, strategy);
}
return rectToClientRect(getDocumentRect(getDocumentElement(element)));
}
// 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 containingBlock = isContainingBlock(currentNode);
const shouldDropCurrentNode = elementIsFixed ? !containingBlock && !currentContainingBlockComputedStyle : !containingBlock && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position);
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
};
}
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.
* @see https://floating-ui.com/docs/autoUpdate
*/
function autoUpdate(reference, floating, update, options) {
if (options === void 0) {
options = {};
}
const {
ancestorScroll: _ancestorScroll = true,
ancestorResize = true,
elementResize = true,
animationFrame = false
} = options;
const ancestorScroll = _ancestorScroll && !animationFrame;
const ancestors = ancestorScroll || ancestorResize ? [...(isElement(reference) ? getOverflowAncestors(reference) : reference.contextElement ? getOverflowAncestors(reference.contextElement) : []), ...getOverflowAncestors(floating)] : [];
ancestors.forEach(ancestor => {
ancestorScroll && ancestor.addEventListener('scroll', update, {
passive: true
});
ancestorResize && ancestor.addEventListener('resize', update);
});
let observer = null;
if (elementResize) {
let initialUpdate = true;
observer = new ResizeObserver(() => {
if (!initialUpdate) {
update();
}
initialUpdate = false;
});
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 };