(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@floating-ui/core')) : typeof define === 'function' && define.amd ? define(['exports', '@floating-ui/core'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FloatingUIDOM = {}, global.FloatingUICore)); })(this, (function (exports, core) { 'use strict'; 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 core.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 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 `` and `` 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 or 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 core.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 core.computePosition(reference, floating, { ...mergedOptions, platform: platformWithCache }); }; Object.defineProperty(exports, 'arrow', { enumerable: true, get: function () { return core.arrow; } }); Object.defineProperty(exports, 'autoPlacement', { enumerable: true, get: function () { return core.autoPlacement; } }); Object.defineProperty(exports, 'detectOverflow', { enumerable: true, get: function () { return core.detectOverflow; } }); Object.defineProperty(exports, 'flip', { enumerable: true, get: function () { return core.flip; } }); Object.defineProperty(exports, 'hide', { enumerable: true, get: function () { return core.hide; } }); Object.defineProperty(exports, 'inline', { enumerable: true, get: function () { return core.inline; } }); Object.defineProperty(exports, 'limitShift', { enumerable: true, get: function () { return core.limitShift; } }); Object.defineProperty(exports, 'offset', { enumerable: true, get: function () { return core.offset; } }); Object.defineProperty(exports, 'shift', { enumerable: true, get: function () { return core.shift; } }); Object.defineProperty(exports, 'size', { enumerable: true, get: function () { return core.size; } }); exports.autoUpdate = autoUpdate; exports.computePosition = computePosition; exports.getOverflowAncestors = getOverflowAncestors; exports.platform = platform; Object.defineProperty(exports, '__esModule', { value: true }); }));