/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.0.0-beta.97 */ import { c as getElementDir, o as closestElementCrossShadowBoundary } from './dom.js'; import { Build } from '@stencil/core/internal/client/index.js'; import { d as debounce } from './debounce.js'; function getSide(placement) { return placement.split('-')[0]; } function getAlignment(placement) { return placement.split('-')[1]; } function getMainAxisFromPlacement(placement) { return ['top', 'bottom'].includes(getSide(placement)) ? 'x' : 'y'; } function getLengthFromAxis(axis) { return axis === 'y' ? 'height' : 'width'; } function computeCoordsFromPlacement(_ref, placement, rtl) { let { reference, floating } = _ref; const commonX = reference.x + reference.width / 2 - floating.width / 2; const commonY = reference.y + reference.height / 2 - floating.height / 2; const mainAxis = getMainAxisFromPlacement(placement); const length = getLengthFromAxis(mainAxis); const commonAlign = reference[length] / 2 - floating[length] / 2; const side = getSide(placement); const isVertical = mainAxis === 'x'; let coords; switch (side) { case 'top': coords = { x: commonX, y: reference.y - floating.height }; break; case 'bottom': coords = { x: commonX, y: reference.y + reference.height }; break; case 'right': coords = { x: reference.x + reference.width, y: commonY }; break; case 'left': coords = { x: reference.x - floating.width, y: commonY }; break; default: coords = { x: reference.x, y: reference.y }; } switch (getAlignment(placement)) { case 'start': coords[mainAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); break; case 'end': coords[mainAxis] += commonAlign * (rtl && isVertical ? -1 : 1); break; } return coords; } /** * Computes the `x` and `y` coordinates that will place the floating element * next to a reference element when it is given a certain positioning strategy. * * This export does not have any `platform` interface logic. You will need to * write one for the platform you are using Floating UI with. */ const computePosition$1 = async (reference, floating, config) => { const { placement = 'bottom', strategy = 'absolute', middleware = [], platform } = config; const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating)); let rects = await platform.getElementRects({ reference, floating, strategy }); let { x, y } = computeCoordsFromPlacement(rects, placement, rtl); let statefulPlacement = placement; let middlewareData = {}; let resetCount = 0; for (let i = 0; i < middleware.length; i++) { const { name, fn } = middleware[i]; const { x: nextX, y: nextY, data, reset } = await fn({ x, y, initialPlacement: placement, placement: statefulPlacement, strategy, middlewareData, rects, platform, elements: { reference, floating } }); x = nextX != null ? nextX : x; y = nextY != null ? nextY : y; middlewareData = { ...middlewareData, [name]: { ...middlewareData[name], ...data } }; if (reset && resetCount <= 50) { resetCount++; if (typeof reset === 'object') { if (reset.placement) { statefulPlacement = reset.placement; } if (reset.rects) { rects = reset.rects === true ? await platform.getElementRects({ reference, floating, strategy }) : reset.rects; } ({ x, y } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); } i = -1; continue; } } return { x, y, placement: statefulPlacement, strategy, middlewareData }; }; function expandPaddingObject(padding) { return { top: 0, right: 0, bottom: 0, left: 0, ...padding }; } function getSideObjectFromPadding(padding) { return typeof padding !== 'number' ? expandPaddingObject(padding) : { top: padding, right: padding, bottom: padding, left: padding }; } function rectToClientRect(rect) { return { ...rect, top: rect.y, left: rect.x, right: rect.x + rect.width, bottom: rect.y + rect.height }; } /** * Resolves with an object of overflow side offsets that determine how much the * element is overflowing a given clipping boundary. * - positive = overflowing the boundary by that number of pixels * - negative = how many pixels left before it will overflow * - 0 = lies flush with the boundary * @see https://floating-ui.com/docs/detectOverflow */ async function detectOverflow(middlewareArguments, options) { var _await$platform$isEle; if (options === void 0) { options = {}; } const { x, y, platform, rects, elements, strategy } = middlewareArguments; const { boundary = 'clippingAncestors', rootBoundary = 'viewport', elementContext = 'floating', altBoundary = false, padding = 0 } = options; const paddingObject = getSideObjectFromPadding(padding); const altContext = elementContext === 'floating' ? 'reference' : 'floating'; const element = elements[altBoundary ? altContext : elementContext]; const clippingClientRect = rectToClientRect(await platform.getClippingRect({ element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))), boundary, rootBoundary, strategy })); const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({ rect: elementContext === 'floating' ? { ...rects.floating, x, y } : rects.reference, offsetParent: await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating)), strategy }) : rects[elementContext]); return { top: clippingClientRect.top - elementClientRect.top + paddingObject.top, bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom, left: clippingClientRect.left - elementClientRect.left + paddingObject.left, right: elementClientRect.right - clippingClientRect.right + paddingObject.right }; } const min$1 = Math.min; const max$1 = Math.max; function within(min$1$1, value, max$1$1) { return max$1(min$1$1, min$1(value, max$1$1)); } /** * Positions an inner element of the floating element such that it is centered * to the reference element. * @see https://floating-ui.com/docs/arrow */ const arrow = options => ({ name: 'arrow', options, async fn(middlewareArguments) { // Since `element` is required, we don't Partial<> the type const { element, padding = 0 } = options != null ? options : {}; const { x, y, placement, rects, platform } = middlewareArguments; if (element == null) { return {}; } const paddingObject = getSideObjectFromPadding(padding); const coords = { x, y }; const axis = getMainAxisFromPlacement(placement); const alignment = getAlignment(placement); const length = getLengthFromAxis(axis); const arrowDimensions = await platform.getDimensions(element); const minProp = axis === 'y' ? 'top' : 'left'; const maxProp = axis === 'y' ? 'bottom' : 'right'; const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length]; const startDiff = coords[axis] - rects.reference[axis]; const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element)); let clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0; if (clientSize === 0) { clientSize = rects.floating[length]; } const centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the floating element if the center // point is outside the floating element's bounds const min = paddingObject[minProp]; const max = clientSize - arrowDimensions[length] - paddingObject[maxProp]; const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference; const offset = within(min, center, max); // Make sure that arrow points at the reference const alignmentPadding = alignment === 'start' ? paddingObject[minProp] : paddingObject[maxProp]; const shouldAddOffset = alignmentPadding > 0 && center !== offset && rects.reference[length] <= rects.floating[length]; const alignmentOffset = shouldAddOffset ? center < min ? min - center : max - center : 0; return { [axis]: coords[axis] - alignmentOffset, data: { [axis]: offset, centerOffset: center - offset } }; } }); const hash$1 = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; function getOppositePlacement(placement) { return placement.replace(/left|right|bottom|top/g, matched => hash$1[matched]); } function getAlignmentSides(placement, rects, rtl) { if (rtl === void 0) { rtl = false; } const alignment = getAlignment(placement); const mainAxis = getMainAxisFromPlacement(placement); const length = getLengthFromAxis(mainAxis); let mainAlignmentSide = mainAxis === 'x' ? alignment === (rtl ? 'end' : 'start') ? 'right' : 'left' : alignment === 'start' ? 'bottom' : 'top'; if (rects.reference[length] > rects.floating[length]) { mainAlignmentSide = getOppositePlacement(mainAlignmentSide); } return { main: mainAlignmentSide, cross: getOppositePlacement(mainAlignmentSide) }; } const hash = { start: 'end', end: 'start' }; function getOppositeAlignmentPlacement(placement) { return placement.replace(/start|end/g, matched => hash[matched]); } const sides = ['top', 'right', 'bottom', 'left']; const allPlacements = /*#__PURE__*/sides.reduce((acc, side) => acc.concat(side, side + "-start", side + "-end"), []); function getPlacementList(alignment, autoAlignment, allowedPlacements) { const allowedPlacementsSortedByAlignment = alignment ? [...allowedPlacements.filter(placement => getAlignment(placement) === alignment), ...allowedPlacements.filter(placement => getAlignment(placement) !== alignment)] : allowedPlacements.filter(placement => getSide(placement) === placement); return allowedPlacementsSortedByAlignment.filter(placement => { if (alignment) { return getAlignment(placement) === alignment || (autoAlignment ? getOppositeAlignmentPlacement(placement) !== placement : false); } return true; }); } /** * Automatically chooses the `placement` which has the most space available. * @see https://floating-ui.com/docs/autoPlacement */ const autoPlacement = function (options) { if (options === void 0) { options = {}; } return { name: 'autoPlacement', options, async fn(middlewareArguments) { var _middlewareData$autoP, _middlewareData$autoP2, _middlewareData$autoP3, _middlewareData$autoP4, _placementsSortedByLe; const { x, y, rects, middlewareData, placement, platform, elements } = middlewareArguments; const { alignment = null, allowedPlacements = allPlacements, autoAlignment = true, ...detectOverflowOptions } = options; const placements = getPlacementList(alignment, autoAlignment, allowedPlacements); const overflow = await detectOverflow(middlewareArguments, detectOverflowOptions); const currentIndex = (_middlewareData$autoP = (_middlewareData$autoP2 = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP2.index) != null ? _middlewareData$autoP : 0; const currentPlacement = placements[currentIndex]; if (currentPlacement == null) { return {}; } const { main, cross } = getAlignmentSides(currentPlacement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))); // Make `computeCoords` start from the right place if (placement !== currentPlacement) { return { x, y, reset: { placement: placements[0] } }; } const currentOverflows = [overflow[getSide(currentPlacement)], overflow[main], overflow[cross]]; const allOverflows = [...((_middlewareData$autoP3 = (_middlewareData$autoP4 = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP4.overflows) != null ? _middlewareData$autoP3 : []), { placement: currentPlacement, overflows: currentOverflows }]; const nextPlacement = placements[currentIndex + 1]; // There are more placements to check if (nextPlacement) { return { data: { index: currentIndex + 1, overflows: allOverflows }, reset: { placement: nextPlacement } }; } const placementsSortedByLeastOverflow = allOverflows.slice().sort((a, b) => a.overflows[0] - b.overflows[0]); const placementThatFitsOnAllSides = (_placementsSortedByLe = placementsSortedByLeastOverflow.find(_ref => { let { overflows } = _ref; return overflows.every(overflow => overflow <= 0); })) == null ? void 0 : _placementsSortedByLe.placement; const resetPlacement = placementThatFitsOnAllSides != null ? placementThatFitsOnAllSides : placementsSortedByLeastOverflow[0].placement; if (resetPlacement !== placement) { return { data: { index: currentIndex + 1, overflows: allOverflows }, reset: { placement: resetPlacement } }; } return {}; } }; }; function getExpandedPlacements(placement) { const oppositePlacement = getOppositePlacement(placement); return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; } /** * Changes the placement of the floating element to one that will fit if the * initially specified `placement` does not. * @see https://floating-ui.com/docs/flip */ const flip = function (options) { if (options === void 0) { options = {}; } return { name: 'flip', options, async fn(middlewareArguments) { var _middlewareData$flip; const { placement, middlewareData, rects, initialPlacement, platform, elements } = middlewareArguments; const { mainAxis: checkMainAxis = true, crossAxis: checkCrossAxis = true, fallbackPlacements: specifiedFallbackPlacements, fallbackStrategy = 'bestFit', flipAlignment = true, ...detectOverflowOptions } = options; const side = getSide(placement); const isBasePlacement = side === initialPlacement; const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); const placements = [initialPlacement, ...fallbackPlacements]; const overflow = await detectOverflow(middlewareArguments, detectOverflowOptions); const overflows = []; let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; if (checkMainAxis) { overflows.push(overflow[side]); } if (checkCrossAxis) { const { main, cross } = getAlignmentSides(placement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))); overflows.push(overflow[main], overflow[cross]); } overflowsData = [...overflowsData, { placement, overflows }]; // One or more sides is overflowing if (!overflows.every(side => side <= 0)) { var _middlewareData$flip$, _middlewareData$flip2; const nextIndex = ((_middlewareData$flip$ = (_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) != null ? _middlewareData$flip$ : 0) + 1; const nextPlacement = placements[nextIndex]; if (nextPlacement) { // Try next placement and re-run the lifecycle return { data: { index: nextIndex, overflows: overflowsData }, reset: { placement: nextPlacement } }; } let resetPlacement = 'bottom'; switch (fallbackStrategy) { case 'bestFit': { var _overflowsData$map$so; const placement = (_overflowsData$map$so = overflowsData.map(d => [d, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$map$so[0].placement; if (placement) { resetPlacement = placement; } break; } case 'initialPlacement': resetPlacement = initialPlacement; break; } if (placement !== resetPlacement) { return { reset: { placement: resetPlacement } }; } } return {}; } }; }; function getSideOffsets(overflow, rect) { return { top: overflow.top - rect.height, right: overflow.right - rect.width, bottom: overflow.bottom - rect.height, left: overflow.left - rect.width }; } function isAnySideFullyClipped(overflow) { return sides.some(side => overflow[side] >= 0); } /** * Provides data to hide the floating element in applicable situations, such as * when it is not in the same clipping context as the reference element. * @see https://floating-ui.com/docs/hide */ const hide = function (_temp) { let { strategy = 'referenceHidden', ...detectOverflowOptions } = _temp === void 0 ? {} : _temp; return { name: 'hide', async fn(middlewareArguments) { const { rects } = middlewareArguments; switch (strategy) { case 'referenceHidden': { const overflow = await detectOverflow(middlewareArguments, { ...detectOverflowOptions, elementContext: 'reference' }); const offsets = getSideOffsets(overflow, rects.reference); return { data: { referenceHiddenOffsets: offsets, referenceHidden: isAnySideFullyClipped(offsets) } }; } case 'escaped': { const overflow = await detectOverflow(middlewareArguments, { ...detectOverflowOptions, altBoundary: true }); const offsets = getSideOffsets(overflow, rects.floating); return { data: { escapedOffsets: offsets, escaped: isAnySideFullyClipped(offsets) } }; } default: { return {}; } } } }; }; async function convertValueToCoords(middlewareArguments, value) { const { placement, platform, elements } = middlewareArguments; const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)); const side = getSide(placement); const alignment = getAlignment(placement); const isVertical = getMainAxisFromPlacement(placement) === 'x'; const mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1; const crossAxisMulti = rtl && isVertical ? -1 : 1; const rawValue = typeof value === 'function' ? value(middlewareArguments) : value; // eslint-disable-next-line prefer-const let { mainAxis, crossAxis, alignmentAxis } = typeof rawValue === 'number' ? { mainAxis: rawValue, crossAxis: 0, alignmentAxis: null } : { mainAxis: 0, crossAxis: 0, alignmentAxis: null, ...rawValue }; if (alignment && typeof alignmentAxis === 'number') { crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis; } return isVertical ? { x: crossAxis * crossAxisMulti, y: mainAxis * mainAxisMulti } : { x: mainAxis * mainAxisMulti, y: crossAxis * crossAxisMulti }; } /** * Displaces the floating element from its reference element. * @see https://floating-ui.com/docs/offset */ const offset = function (value) { if (value === void 0) { value = 0; } return { name: 'offset', options: value, async fn(middlewareArguments) { const { x, y } = middlewareArguments; const diffCoords = await convertValueToCoords(middlewareArguments, value); return { x: x + diffCoords.x, y: y + diffCoords.y, data: diffCoords }; } }; }; function getCrossAxis(axis) { return axis === 'x' ? 'y' : 'x'; } /** * Shifts the floating element in order to keep it in view when it will overflow * a clipping boundary. * @see https://floating-ui.com/docs/shift */ const shift = function (options) { if (options === void 0) { options = {}; } return { name: 'shift', options, async fn(middlewareArguments) { const { x, y, placement } = middlewareArguments; const { mainAxis: checkMainAxis = true, crossAxis: checkCrossAxis = false, limiter = { fn: _ref => { let { x, y } = _ref; return { x, y }; } }, ...detectOverflowOptions } = options; const coords = { x, y }; const overflow = await detectOverflow(middlewareArguments, detectOverflowOptions); const mainAxis = getMainAxisFromPlacement(getSide(placement)); const crossAxis = getCrossAxis(mainAxis); let mainAxisCoord = coords[mainAxis]; let crossAxisCoord = coords[crossAxis]; if (checkMainAxis) { const minSide = mainAxis === 'y' ? 'top' : 'left'; const maxSide = mainAxis === 'y' ? 'bottom' : 'right'; const min = mainAxisCoord + overflow[minSide]; const max = mainAxisCoord - overflow[maxSide]; mainAxisCoord = within(min, mainAxisCoord, max); } if (checkCrossAxis) { const minSide = crossAxis === 'y' ? 'top' : 'left'; const maxSide = crossAxis === 'y' ? 'bottom' : 'right'; const min = crossAxisCoord + overflow[minSide]; const max = crossAxisCoord - overflow[maxSide]; crossAxisCoord = within(min, crossAxisCoord, max); } const limitedCoords = limiter.fn({ ...middlewareArguments, [mainAxis]: mainAxisCoord, [crossAxis]: crossAxisCoord }); return { ...limitedCoords, data: { x: limitedCoords.x - x, y: limitedCoords.y - y } }; } }; }; function isWindow(value) { return value && value.document && value.location && value.alert && value.setInterval; } 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(element) { return getWindow(element).getComputedStyle(element); } function getNodeName(node) { return isWindow(node) ? '' : node ? (node.nodeName || '').toLowerCase() : ''; } function getUAString() { const uaData = navigator.userAgentData; if (uaData != null && uaData.brands) { return uaData.brands.map(item => item.brand + "/" + item.version).join(' '); } 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) { // Firefox wants us to check `-x` and `-y` variations as well const { overflow, overflowX, overflowY, display } = getComputedStyle(element); return /auto|scroll|overlay|hidden/.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(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' || 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 , 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 getBoundingClientRect(element, includeScale, isFixedStrategy) { var _win$visualViewport$o, _win$visualViewport, _win$visualViewport$o2, _win$visualViewport2; if (includeScale === void 0) { includeScale = false; } if (isFixedStrategy === void 0) { isFixedStrategy = 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; } const win = isElement(element) ? getWindow(element) : window; const addVisualOffsets = !isLayoutViewport() && isFixedStrategy; const x = (clientRect.left + (addVisualOffsets ? (_win$visualViewport$o = (_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) != null ? _win$visualViewport$o : 0 : 0)) / scaleX; const y = (clientRect.top + (addVisualOffsets ? (_win$visualViewport$o2 = (_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) != null ? _win$visualViewport$o2 : 0 : 0)) / scaleY; const width = clientRect.width / scaleX; const height = clientRect.height / scaleY; 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 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, // @ts-ignore - checked above (TS 4.1 compat) isOffsetParentAnElement && isScaled(offsetParent), strategy === 'fixed'); 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; } 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); if (isShadowRoot(currentNode)) { currentNode = currentNode.host; } while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { if (isContainingBlock(currentNode)) { return currentNode; } else { const parent = currentNode.parentNode; currentNode = isShadowRoot(parent) ? parent.host : parent; } } 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) { if (isHTMLElement(element)) { return { width: element.offsetWidth, height: element.offsetHeight }; } const rect = getBoundingClientRect(element); return { width: rect.width, height: rect.height }; } 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' || 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; } // 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, 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 }; } // of the `` and `` 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(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); const target = isBody ? [win].concat(win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []) : scrollableAncestor; const updatedList = list.concat(target); return isBody ? updatedList : // @ts-ignore: isBody tells us target will be an HTMLElement here updatedList.concat(getOverflowAncestors(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 getNearestParentCapableOfEscapingClipping(element, clippingAncestors) { let currentNode = element; while (currentNode && !isLastTraversableNode(currentNode) && // @ts-expect-error !clippingAncestors.includes(currentNode)) { if (isElement(currentNode) && ['absolute', 'fixed'].includes(getComputedStyle(currentNode).position)) { break; } const parentNode = getParentNode(currentNode); currentNode = isShadowRoot(parentNode) ? parentNode.host : parentNode; } return currentNode; } function getInnerBoundingClientRect(element, strategy) { const clientRect = getBoundingClientRect(element, false, strategy === 'fixed'); 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 getClientRectFromClippingAncestor(element, clippingParent, strategy) { if (clippingParent === 'viewport') { return rectToClientRect(getViewportRect(element, strategy)); } if (isElement(clippingParent)) { return getInnerBoundingClientRect(clippingParent, strategy); } return rectToClientRect(getDocumentRect(getDocumentElement(element))); } // A "clipping ancestor" is an overflowable container with the characteristic of // clipping (or hiding) overflowing elements with a position different from // `initial` function getClippingAncestors(element) { const clippingAncestors = getOverflowAncestors(element); const nearestEscapableParent = getNearestParentCapableOfEscapingClipping(element, clippingAncestors); let clipperElement = null; if (nearestEscapableParent && isHTMLElement(nearestEscapableParent)) { const offsetParent = getOffsetParent(nearestEscapableParent); if (isOverflowElement(nearestEscapableParent)) { clipperElement = nearestEscapableParent; } else if (isHTMLElement(offsetParent)) { clipperElement = offsetParent; } } if (!isElement(clipperElement)) { return []; } // @ts-ignore isElement check ensures we return Array return clippingAncestors.filter(clippingAncestors => clipperElement && isElement(clippingAncestors) && contains(clippingAncestors, clipperElement) && getNodeName(clippingAncestors) !== 'body'); } // 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 mainClippingAncestors = boundary === 'clippingAncestors' ? getClippingAncestors(element) : [].concat(boundary); const clippingAncestors = [...mainClippingAncestors, 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, getElementRects: _ref => { let { reference, floating, strategy } = _ref; return { reference: getRectRelativeToOffsetParent(reference, getOffsetParent(floating), strategy), floating: { ...getDimensions(floating), x: 0, y: 0 } }; }, getClientRects: element => Array.from(element.getClientRects()), isRTL: element => getComputedStyle(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) : []), ...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); 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) => computePosition$1(reference, floating, { platform, ...options }); /** * This module helps users provide custom configuration for component internals. * * @internal */ const configOverrides = globalThis["calciteComponentsConfig"]; const config = { /** * We apply a custom fix to improve positioning for non-Chromium browsers. * The fix comes at a performance cost, so provides users a way to opt-out if necessary. * * @internal */ floatingUINonChromiumPositioningFix: true, ...configOverrides }; const floatingUIBrowserCheck = patchFloatingUiForNonChromiumBrowsers(); async function patchFloatingUiForNonChromiumBrowsers() { function getUAString() { const uaData = navigator.userAgentData; if (uaData === null || uaData === void 0 ? void 0 : uaData.brands) { return uaData.brands.map((item) => `${item.brand}/${item.version}`).join(" "); } return navigator.userAgent; } if (Build.isBrowser && config.floatingUINonChromiumPositioningFix && // ⚠️ browser-sniffing is not a best practice and should be avoided ⚠️ /firefox|safari/i.test(getUAString())) { const { getClippingRect, getElementRects, getOffsetParent } = await import('./nonChromiumPlatformUtils.js'); platform.getClippingRect = getClippingRect; platform.getOffsetParent = getOffsetParent; platform.getElementRects = getElementRects; } } const placementDataAttribute = "data-placement"; /** * Exported for testing purposes only */ const repositionDebounceTimeout = 100; const effectivePlacements = [ "top", "bottom", "right", "left", "top-start", "top-end", "bottom-start", "bottom-end", "right-start", "right-end", "left-start", "left-end" ]; const defaultMenuPlacement = "bottom-start"; const FloatingCSS = { animation: "calcite-floating-ui-anim", animationActive: "calcite-floating-ui-anim--active" }; function getMiddleware({ placement, disableFlip, flipPlacements, offsetDistance, offsetSkidding, arrowEl, type }) { const defaultMiddleware = [shift(), hide()]; if (type === "menu") { return [ ...defaultMiddleware, flip({ fallbackPlacements: flipPlacements || ["top-start", "top", "top-end", "bottom-start", "bottom", "bottom-end"] }) ]; } if (type === "popover" || type === "tooltip") { const middleware = [ ...defaultMiddleware, offset({ mainAxis: typeof offsetDistance === "number" ? offsetDistance : 0, crossAxis: typeof offsetSkidding === "number" ? offsetSkidding : 0 }) ]; if (placement === "auto" || placement === "auto-start" || placement === "auto-end") { middleware.push(autoPlacement({ alignment: placement === "auto-start" ? "start" : placement === "auto-end" ? "end" : null })); } else if (!disableFlip) { middleware.push(flip(flipPlacements ? { fallbackPlacements: flipPlacements } : {})); } if (arrowEl) { middleware.push(arrow({ element: arrowEl })); } return middleware; } return []; } function filterComputedPlacements(placements, el) { const filteredPlacements = placements.filter((placement) => effectivePlacements.includes(placement)); if (filteredPlacements.length !== placements.length) { console.warn(`${el.tagName}: Invalid value found in: flipPlacements. Try any of these: ${effectivePlacements .map((placement) => `"${placement}"`) .join(", ") .trim()}`, { el }); } return filteredPlacements; } /* In floating-ui, "*-start" and "*-end" are already flipped in RTL. There is no need for our "*-leading" and "*-trailing" values anymore. https://github.com/floating-ui/floating-ui/issues/1530 https://github.com/floating-ui/floating-ui/issues/1563 */ function getEffectivePlacement(floatingEl, placement) { const placements = ["left", "right"]; if (getElementDir(floatingEl) === "rtl") { placements.reverse(); } return placement .replace(/-leading/gi, "-start") .replace(/-trailing/gi, "-end") .replace(/leading/gi, placements[0]) .replace(/trailing/gi, placements[1]); } /** * Convenience function to manage `reposition` calls for FloatingUIComponents that use `positionFloatingUI. * * Note: this is not needed for components that use `calcite-popover`. * * @param component * @param options * @param options.referenceEl * @param options.floatingEl * @param options.overlayPositioning * @param options.placement * @param options.disableFlip * @param options.flipPlacements * @param options.offsetDistance * @param options.offsetSkidding * @param options.arrowEl * @param options.type * @param delayed */ async function reposition(component, options, delayed = false) { if (!component.open) { return; } return delayed ? debouncedReposition(options) : positionFloatingUI(options); } const debouncedReposition = debounce(positionFloatingUI, repositionDebounceTimeout, { leading: true, maxWait: repositionDebounceTimeout }); /** * Positions the floating element relative to the reference element. * * **Note:** exported for testing purposes only * * @param root0 * @param root0.referenceEl * @param root0.floatingEl * @param root0.overlayPositioning * @param root0.placement * @param root0.disableFlip * @param root0.flipPlacements * @param root0.offsetDistance * @param root0.offsetSkidding * @param root0.arrowEl * @param root0.type * @param root0.includeArrow */ async function positionFloatingUI({ referenceEl, floatingEl, overlayPositioning = "absolute", placement, disableFlip, flipPlacements, offsetDistance, offsetSkidding, includeArrow = false, arrowEl, type }) { var _a; if (!referenceEl || !floatingEl || (includeArrow && !arrowEl)) { return null; } await floatingUIBrowserCheck; const { x, y, placement: effectivePlacement, strategy: position, middlewareData } = await computePosition(referenceEl, floatingEl, { strategy: overlayPositioning, placement: placement === "auto" || placement === "auto-start" || placement === "auto-end" ? undefined : getEffectivePlacement(floatingEl, placement), middleware: getMiddleware({ placement, disableFlip, flipPlacements, offsetDistance, offsetSkidding, arrowEl, type }) }); if (middlewareData === null || middlewareData === void 0 ? void 0 : middlewareData.arrow) { const { x: arrowX, y: arrowY } = middlewareData.arrow; Object.assign(arrowEl.style, { left: arrowX != null ? `${arrowX}px` : "", top: arrowY != null ? `${arrowY}px` : "" }); } const referenceHidden = (_a = middlewareData === null || middlewareData === void 0 ? void 0 : middlewareData.hide) === null || _a === void 0 ? void 0 : _a.referenceHidden; const visibility = referenceHidden ? "hidden" : null; const pointerEvents = visibility ? "none" : null; floatingEl.setAttribute(placementDataAttribute, effectivePlacement); const transform = `translate(${Math.round(x)}px,${Math.round(y)}px)`; Object.assign(floatingEl.style, { visibility, pointerEvents, position, top: "0", left: "0", transform }); } /** * Exported for testing purposes only * * @internal */ const cleanupMap = new WeakMap(); /** * Helper to set up floating element interactions on connectedCallback. * * @param component * @param referenceEl * @param floatingEl */ function connectFloatingUI(component, referenceEl, floatingEl) { if (!floatingEl || !referenceEl) { return; } disconnectFloatingUI(component, referenceEl, floatingEl); const position = component.overlayPositioning; // ensure position matches for initial positioning floatingEl.style.position = position; if (position === "absolute") { moveOffScreen(floatingEl); } const runAutoUpdate = Build.isBrowser ? autoUpdate : (_refEl, _floatingEl, updateCallback) => { updateCallback(); return () => { /* noop */ }; }; cleanupMap.set(component, runAutoUpdate(referenceEl, floatingEl, () => component.reposition())); } /** * Helper to tear down floating element interactions on disconnectedCallback. * * @param component * @param referenceEl * @param floatingEl */ function disconnectFloatingUI(component, referenceEl, floatingEl) { if (!floatingEl || !referenceEl) { return; } getTransitionTarget(floatingEl).removeEventListener("transitionend", handleTransitionElTransitionEnd); const cleanup = cleanupMap.get(component); if (cleanup) { cleanup(); } cleanupMap.delete(component); } const visiblePointerSize = 4; /** * Default offset the position of the floating element away from the reference element. * * @default 6 */ const defaultOffsetDistance = Math.ceil(Math.hypot(visiblePointerSize, visiblePointerSize)); /** * This utils applies floating element styles to avoid affecting layout when closed. * * This should be called when the closing transition will start. * * @param floatingEl */ function updateAfterClose(floatingEl) { if (!floatingEl || floatingEl.style.position !== "absolute") { return; } getTransitionTarget(floatingEl).addEventListener("transitionend", handleTransitionElTransitionEnd); } function getTransitionTarget(floatingEl) { // assumes floatingEl w/ shadowRoot is a FloatingUIComponent return floatingEl.shadowRoot || floatingEl; } function handleTransitionElTransitionEnd(event) { const floatingTransitionEl = event.target; if ( // using any prop from floating-ui transition event.propertyName === "opacity" && floatingTransitionEl.classList.contains(FloatingCSS.animation)) { const floatingEl = getFloatingElFromTransitionTarget(floatingTransitionEl); moveOffScreen(floatingEl); getTransitionTarget(floatingEl).removeEventListener("transitionend", handleTransitionElTransitionEnd); } } function moveOffScreen(floatingEl) { floatingEl.style.transform = ""; floatingEl.style.top = "-99999px"; floatingEl.style.left = "-99999px"; } function getFloatingElFromTransitionTarget(floatingTransitionEl) { return closestElementCrossShadowBoundary(floatingTransitionEl, `[${placementDataAttribute}]`); } export { FloatingCSS as F, disconnectFloatingUI as a, defaultOffsetDistance as b, connectFloatingUI as c, defaultMenuPlacement as d, rectToClientRect as e, filterComputedPlacements as f, reposition as r, updateAfterClose as u };