From e862c16e30802c25fefec507a3e4036d7cd33e73 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 10 Jan 2026 13:28:56 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=E5=A2=9E=E5=8A=A0=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContextMenu/ContextMenu.razor.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/BootstrapBlazor/Components/ContextMenu/ContextMenu.razor.js b/src/BootstrapBlazor/Components/ContextMenu/ContextMenu.razor.js index be1d38622d8..68a026e275a 100644 --- a/src/BootstrapBlazor/Components/ContextMenu/ContextMenu.razor.js +++ b/src/BootstrapBlazor/Components/ContextMenu/ContextMenu.razor.js @@ -1,4 +1,4 @@ -import Data from "../../modules/data.js" +import Data from "../../modules/data.js" import EventHandler from "../../modules/event-handler.js" import { createPopper, computePosition } from '../../modules/floating-ui.js' import { registerBootstrapBlazorModule } from "../../modules/utility.js" @@ -36,21 +36,23 @@ export function init(id) { } export function show(id, event) { - const cm = Data.get(id) + setTimeout(() => { + const cm = Data.get(id) - if (cm) { - const el = cm.el - const zone = cm.zone + if (cm) { + const el = cm.el + const zone = cm.zone - const body = document.body - body.appendChild(el) + const body = document.body + body.appendChild(el) - if (cm.popper) { - cm.popper() - } + if (cm.popper) { + cm.popper() + } - cm.popper = createPopper(zone, el, () => showContextMenu(zone, el, event)) - } + cm.popper = createPopper(zone, el, () => showContextMenu(zone, el, event)) + } + }, 0); } export function dispose(id) { From 46b738467831a92d60507ba4baf8cce33f81aaac Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 10 Jan 2026 13:30:04 +0800 Subject: [PATCH 2/5] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E8=84=9A=E6=9C=AC=E5=88=B0=E6=9C=80=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/floating-ui/floating-ui.core.esm.js | 346 ++++------- .../lib/floating-ui/floating-ui.dom.esm.js | 572 ++++++++++-------- .../floating-ui/floating-ui.utils.dom.esm.js | 161 +++++ .../lib/floating-ui/floating-ui.utils.esm.js | 139 +++++ 4 files changed, 757 insertions(+), 461 deletions(-) create mode 100644 src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.utils.dom.esm.js create mode 100644 src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.utils.esm.js diff --git a/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.core.esm.js b/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.core.esm.js index 00a1a120d11..d296b316947 100644 --- a/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.core.esm.js +++ b/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.core.esm.js @@ -1,31 +1,19 @@ -function getAlignment(placement) { - return placement.split('-')[1]; -} - -function getLengthFromAxis(axis) { - return axis === 'y' ? 'height' : 'width'; -} - -function getSide(placement) { - return placement.split('-')[0]; -} - -function getMainAxisFromPlacement(placement) { - return ['top', 'bottom'].includes(getSide(placement)) ? 'x' : 'y'; -} +import { getSideAxis, getAlignmentAxis, getAxisLength, getSide, getAlignment, evaluate, getPaddingObject, rectToClientRect, min, clamp, placements, getAlignmentSides, getOppositeAlignmentPlacement, getOppositePlacement, getExpandedPlacements, getOppositeAxisPlacements, sides, max, getOppositeAxis } from './floating-ui.utils.esm.js'; +export { rectToClientRect } from './floating-ui.utils.esm.js'; function computeCoordsFromPlacement(_ref, placement, rtl) { let { reference, floating } = _ref; + const sideAxis = getSideAxis(placement); + const alignmentAxis = getAlignmentAxis(placement); + const alignLength = getAxisLength(alignmentAxis); + const side = getSide(placement); + const isVertical = sideAxis === 'y'; 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'; + const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; let coords; switch (side) { case 'top': @@ -60,10 +48,10 @@ function computeCoordsFromPlacement(_ref, placement, rtl) { } switch (getAlignment(placement)) { case 'start': - coords[mainAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); + coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); break; case 'end': - coords[mainAxis] += commonAlign * (rtl && isVertical ? -1 : 1); + coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1); break; } return coords; @@ -71,7 +59,7 @@ function computeCoordsFromPlacement(_ref, placement, rtl) { /** * 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. + * next to a given reference element. * * This export does not have any `platform` interface logic. You will need to * write one for the platform you are using Floating UI with. @@ -149,7 +137,6 @@ const computePosition = async (reference, floating, config) => { } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); } i = -1; - continue; } } return { @@ -161,39 +148,6 @@ const computePosition = async (reference, floating, config) => { }; }; -function evaluate(value, param) { - return typeof value === 'function' ? value(param) : value; -} - -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 on each side. @@ -222,7 +176,7 @@ async function detectOverflow(state, options) { altBoundary = false, padding = 0 } = evaluate(options, state); - const paddingObject = getSideObjectFromPadding(padding); + const paddingObject = getPaddingObject(padding); const altContext = elementContext === 'floating' ? 'reference' : 'floating'; const element = elements[altBoundary ? altContext : elementContext]; const clippingClientRect = rectToClientRect(await platform.getClippingRect({ @@ -232,9 +186,10 @@ async function detectOverflow(state, options) { strategy })); const rect = elementContext === 'floating' ? { - ...rects.floating, x, - y + y, + width: rects.floating.width, + height: rects.floating.height } : rects.reference; const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating)); const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || { @@ -245,6 +200,7 @@ async function detectOverflow(state, options) { y: 1 }; const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({ + elements, rect, offsetParent, strategy @@ -257,13 +213,6 @@ async function detectOverflow(state, options) { }; } -const min = Math.min; -const max = Math.max; - -function within(min$1, value, max$1) { - return max(min$1, min(value, max$1)); -} - /** * Provides data to position an inner element of the floating element so that it * appears centered to the reference element. @@ -279,7 +228,8 @@ const arrow = options => ({ placement, rects, platform, - elements + elements, + middlewareData } = state; // Since `element` is required, we don't Partial<> the type. const { @@ -289,13 +239,13 @@ const arrow = options => ({ if (element == null) { return {}; } - const paddingObject = getSideObjectFromPadding(padding); + const paddingObject = getPaddingObject(padding); const coords = { x, y }; - const axis = getMainAxisFromPlacement(placement); - const length = getLengthFromAxis(axis); + const axis = getAlignmentAxis(placement); + const length = getAxisLength(axis); const arrowDimensions = await platform.getDimensions(element); const isYAxis = axis === 'y'; const minProp = isYAxis ? 'top' : 'left'; @@ -323,62 +273,28 @@ const arrow = options => ({ const min$1 = minPadding; const max = clientSize - arrowDimensions[length] - maxPadding; const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference; - const offset = within(min$1, center, max); + const offset = clamp(min$1, center, max); // If the reference is small enough that the arrow's padding causes it to // to point to nothing for an aligned placement, adjust the offset of the - // floating element itself. This stops `shift()` from taking action, but can - // be worked around by calling it again after the `arrow()` if desired. - const shouldAddOffset = getAlignment(placement) != null && center != offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0; - const alignmentOffset = shouldAddOffset ? center < min$1 ? min$1 - center : max - center : 0; + // floating element itself. To ensure `shift()` continues to take action, + // a single reset is performed when this is true. + const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0; + const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0; return { - [axis]: coords[axis] - alignmentOffset, + [axis]: coords[axis] + alignmentOffset, data: { [axis]: offset, - centerOffset: center - offset + alignmentOffset - } + centerOffset: center - offset - alignmentOffset, + ...(shouldAddOffset && { + alignmentOffset + }) + }, + reset: shouldAddOffset }; } }); -const sides = ['top', 'right', 'bottom', 'left']; -const allPlacements = /*#__PURE__*/sides.reduce((acc, side) => acc.concat(side, side + "-start", side + "-end"), []); - -const oppositeSideMap = { - left: 'right', - right: 'left', - bottom: 'top', - top: 'bottom' -}; -function getOppositePlacement(placement) { - return placement.replace(/left|right|bottom|top/g, side => oppositeSideMap[side]); -} - -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 oppositeAlignmentMap = { - start: 'end', - end: 'start' -}; -function getOppositeAlignmentPlacement(placement) { - return placement.replace(/start|end/g, alignment => oppositeAlignmentMap[alignment]); -} - 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 => { @@ -413,36 +329,33 @@ const autoPlacement = function (options) { const { crossAxis = false, alignment, - allowedPlacements = allPlacements, + allowedPlacements = placements, autoAlignment = true, ...detectOverflowOptions } = evaluate(options, state); - const placements = alignment !== undefined || allowedPlacements === allPlacements ? getPlacementList(alignment || null, autoAlignment, allowedPlacements) : allowedPlacements; + const placements$1 = alignment !== undefined || allowedPlacements === placements ? getPlacementList(alignment || null, autoAlignment, allowedPlacements) : allowedPlacements; const overflow = await detectOverflow(state, detectOverflowOptions); const currentIndex = ((_middlewareData$autoP = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP.index) || 0; - const currentPlacement = placements[currentIndex]; + const currentPlacement = placements$1[currentIndex]; if (currentPlacement == null) { return {}; } - const { - main, - cross - } = getAlignmentSides(currentPlacement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))); + const alignmentSides = getAlignmentSides(currentPlacement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))); // Make `computeCoords` start from the right place. if (placement !== currentPlacement) { return { reset: { - placement: placements[0] + placement: placements$1[0] } }; } - const currentOverflows = [overflow[getSide(currentPlacement)], overflow[main], overflow[cross]]; + const currentOverflows = [overflow[getSide(currentPlacement)], overflow[alignmentSides[0]], overflow[alignmentSides[1]]]; const allOverflows = [...(((_middlewareData$autoP2 = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP2.overflows) || []), { placement: currentPlacement, overflows: currentOverflows }]; - const nextPlacement = placements[currentIndex + 1]; + const nextPlacement = placements$1[currentIndex + 1]; // There are more placements to check. if (nextPlacement) { @@ -485,40 +398,6 @@ const autoPlacement = function (options) { }; }; -function getExpandedPlacements(placement) { - const oppositePlacement = getOppositePlacement(placement); - return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; -} - -function getSideList(side, isStart, rtl) { - const lr = ['left', 'right']; - const rl = ['right', 'left']; - const tb = ['top', 'bottom']; - const bt = ['bottom', 'top']; - switch (side) { - case 'top': - case 'bottom': - if (rtl) return isStart ? rl : lr; - return isStart ? lr : rl; - case 'left': - case 'right': - return isStart ? tb : bt; - default: - return []; - } -} -function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { - const alignment = getAlignment(placement); - let list = getSideList(getSide(placement), direction === 'start', rtl); - if (alignment) { - list = list.map(side => side + "-" + alignment); - if (flipAlignment) { - list = list.concat(list.map(getOppositeAlignmentPlacement)); - } - } - return list; -} - /** * Optimizes the visibility of the floating element by flipping the `placement` * in order to keep it in view when the preferred placement(s) will overflow the @@ -533,7 +412,7 @@ const flip = function (options) { name: 'flip', options, async fn(state) { - var _middlewareData$flip; + var _middlewareData$arrow, _middlewareData$flip; const { placement, middlewareData, @@ -551,11 +430,21 @@ const flip = function (options) { flipAlignment = true, ...detectOverflowOptions } = evaluate(options, state); + + // If a reset by the arrow was caused due to an alignment offset being + // added, we should skip any logic now since `flip()` has already done its + // work. + // https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643 + if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { + return {}; + } const side = getSide(placement); + const initialSideAxis = getSideAxis(initialPlacement); const isBasePlacement = getSide(initialPlacement) === initialPlacement; const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)); const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); - if (!specifiedFallbackPlacements && fallbackAxisSideDirection !== 'none') { + const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== 'none'; + if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) { fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); } const placements = [initialPlacement, ...fallbackPlacements]; @@ -566,11 +455,8 @@ const flip = function (options) { overflows.push(overflow[side]); } if (checkCrossAxis) { - const { - main, - cross - } = getAlignmentSides(placement, rects, rtl); - overflows.push(overflow[main], overflow[cross]); + const sides = getAlignmentSides(placement, rects, rtl); + overflows.push(overflow[sides[0]], overflow[sides[1]]); } overflowsData = [...overflowsData, { placement, @@ -583,16 +469,22 @@ const flip = function (options) { const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 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 - } - }; + const ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== getSideAxis(nextPlacement) : false; + if (!ignoreCrossAxisOverflow || + // We leave the current main axis only if every placement on that axis + // overflows the main axis. + overflowsData.every(d => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) { + // Try next placement and re-run the lifecycle. + return { + data: { + index: nextIndex, + overflows: overflowsData + }, + reset: { + placement: nextPlacement + } + }; + } } // First, find the candidates that fit on the mainAxis side of overflow, @@ -604,8 +496,17 @@ const flip = function (options) { switch (fallbackStrategy) { case 'bestFit': { - var _overflowsData$map$so; - const placement = (_overflowsData$map$so = overflowsData.map(d => [d.placement, 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]; + var _overflowsData$filter2; + const placement = (_overflowsData$filter2 = overflowsData.filter(d => { + if (hasFallbackAxisSideDirection) { + const currentSideAxis = getSideAxis(d.placement); + return currentSideAxis === initialSideAxis || + // Create a bias to the `y` side axis due to horizontal + // reading directions favoring greater width. + currentSideAxis === 'y'; + } + return true; + }).map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0]; if (placement) { resetPlacement = placement; } @@ -756,7 +657,7 @@ const inline = function (options) { const nativeClientRects = Array.from((await (platform.getClientRects == null ? void 0 : platform.getClientRects(elements.reference))) || []); const clientRects = getRectsByLine(nativeClientRects); const fallback = rectToClientRect(getBoundingRect(nativeClientRects)); - const paddingObject = getSideObjectFromPadding(padding); + const paddingObject = getPaddingObject(padding); function getBoundingClientRect() { // There are two rects and they are disjoined. if (clientRects.length === 2 && clientRects[0].left > clientRects[1].right && x != null && y != null) { @@ -766,7 +667,7 @@ const inline = function (options) { // There are 2 or more connected rects. if (clientRects.length >= 2) { - if (getMainAxisFromPlacement(placement) === 'x') { + if (getSideAxis(placement) === 'y') { const firstRect = clientRects[0]; const lastRect = clientRects[clientRects.length - 1]; const isTop = getSide(placement) === 'top'; @@ -829,6 +730,11 @@ const inline = function (options) { }; }; +const originSides = /*#__PURE__*/new Set(['left', 'top']); + +// For type backwards-compatibility, the `OffsetOptions` type was also +// Derivable. + async function convertValueToCoords(state, options) { const { placement, @@ -838,8 +744,8 @@ async function convertValueToCoords(state, options) { 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 isVertical = getSideAxis(placement) === 'y'; + const mainAxisMulti = originSides.has(side) ? -1 : 1; const crossAxisMulti = rtl && isVertical ? -1 : 1; const rawValue = evaluate(options, state); @@ -853,10 +759,9 @@ async function convertValueToCoords(state, options) { crossAxis: 0, alignmentAxis: null } : { - mainAxis: 0, - crossAxis: 0, - alignmentAxis: null, - ...rawValue + mainAxis: rawValue.mainAxis || 0, + crossAxis: rawValue.crossAxis || 0, + alignmentAxis: rawValue.alignmentAxis }; if (alignment && typeof alignmentAxis === 'number') { crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis; @@ -885,24 +790,32 @@ const offset = function (options) { name: 'offset', options, async fn(state) { + var _middlewareData$offse, _middlewareData$arrow; const { x, - y + y, + placement, + middlewareData } = state; const diffCoords = await convertValueToCoords(state, options); + + // If the placement is the same and the arrow caused an alignment offset + // then we don't need to change the positioning coordinates. + if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { + return {}; + } return { x: x + diffCoords.x, y: y + diffCoords.y, - data: diffCoords + data: { + ...diffCoords, + placement + } }; } }; }; -function getCrossAxis(axis) { - return axis === 'x' ? 'y' : 'x'; -} - /** * Optimizes the visibility of the floating element by shifting it in order to * keep it in view when it will overflow the clipping boundary. @@ -943,8 +856,8 @@ const shift = function (options) { y }; const overflow = await detectOverflow(state, detectOverflowOptions); - const mainAxis = getMainAxisFromPlacement(getSide(placement)); - const crossAxis = getCrossAxis(mainAxis); + const crossAxis = getSideAxis(getSide(placement)); + const mainAxis = getOppositeAxis(crossAxis); let mainAxisCoord = coords[mainAxis]; let crossAxisCoord = coords[crossAxis]; if (checkMainAxis) { @@ -952,14 +865,14 @@ const shift = function (options) { const maxSide = mainAxis === 'y' ? 'bottom' : 'right'; const min = mainAxisCoord + overflow[minSide]; const max = mainAxisCoord - overflow[maxSide]; - mainAxisCoord = within(min, mainAxisCoord, max); + mainAxisCoord = clamp(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); + crossAxisCoord = clamp(min, crossAxisCoord, max); } const limitedCoords = limiter.fn({ ...state, @@ -970,7 +883,11 @@ const shift = function (options) { ...limitedCoords, data: { x: limitedCoords.x - x, - y: limitedCoords.y - y + y: limitedCoords.y - y, + enabled: { + [mainAxis]: checkMainAxis, + [crossAxis]: checkCrossAxis + } } }; } @@ -1002,8 +919,8 @@ const limitShift = function (options) { x, y }; - const mainAxis = getMainAxisFromPlacement(placement); - const crossAxis = getCrossAxis(mainAxis); + const crossAxis = getSideAxis(placement); + const mainAxis = getOppositeAxis(crossAxis); let mainAxisCoord = coords[mainAxis]; let crossAxisCoord = coords[crossAxis]; const rawOffset = evaluate(offset, state); @@ -1028,7 +945,7 @@ const limitShift = function (options) { if (checkCrossAxis) { var _middlewareData$offse, _middlewareData$offse2; const len = mainAxis === 'y' ? 'width' : 'height'; - const isOriginSide = ['top', 'left'].includes(getSide(placement)); + const isOriginSide = originSides.has(getSide(placement)); const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis); const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0); if (crossAxisCoord < limitMin) { @@ -1059,6 +976,7 @@ const size = function (options) { name: 'size', options, async fn(state) { + var _state$middlewareData, _state$middlewareData2; const { placement, rects, @@ -1072,8 +990,7 @@ const size = function (options) { const overflow = await detectOverflow(state, detectOverflowOptions); const side = getSide(placement); const alignment = getAlignment(placement); - const axis = getMainAxisFromPlacement(placement); - const isXAxis = axis === 'x'; + const isYAxis = getSideAxis(placement) === 'y'; const { width, height @@ -1087,24 +1004,25 @@ const size = function (options) { widthSide = side; heightSide = alignment === 'end' ? 'top' : 'bottom'; } - const overflowAvailableHeight = height - overflow[heightSide]; - const overflowAvailableWidth = width - overflow[widthSide]; + const maximumClippingHeight = height - overflow.top - overflow.bottom; + const maximumClippingWidth = width - overflow.left - overflow.right; + const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight); + const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth); const noShift = !state.middlewareData.shift; let availableHeight = overflowAvailableHeight; let availableWidth = overflowAvailableWidth; - if (isXAxis) { - const maximumClippingWidth = width - overflow.left - overflow.right; - availableWidth = alignment || noShift ? min(overflowAvailableWidth, maximumClippingWidth) : maximumClippingWidth; - } else { - const maximumClippingHeight = height - overflow.top - overflow.bottom; - availableHeight = alignment || noShift ? min(overflowAvailableHeight, maximumClippingHeight) : maximumClippingHeight; + if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) { + availableWidth = maximumClippingWidth; + } + if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) { + availableHeight = maximumClippingHeight; } if (noShift && !alignment) { const xMin = max(overflow.left, 0); const xMax = max(overflow.right, 0); const yMin = max(overflow.top, 0); const yMax = max(overflow.bottom, 0); - if (isXAxis) { + if (isYAxis) { availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right)); } else { availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom)); @@ -1128,4 +1046,4 @@ const size = function (options) { }; }; -export { arrow, autoPlacement, computePosition, detectOverflow, flip, hide, inline, limitShift, offset, rectToClientRect, shift, size }; +export { arrow, autoPlacement, computePosition, detectOverflow, flip, hide, inline, limitShift, offset, shift, size }; diff --git a/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.dom.esm.js b/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.dom.esm.js index 52ac1e2bf6c..d8d2bfffab0 100644 --- a/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.dom.esm.js +++ b/src/BootstrapBlazor/wwwroot/lib/floating-ui/floating-ui.dom.esm.js @@ -1,76 +1,7 @@ -import { rectToClientRect, computePosition as computePosition$1 } from './floating-ui.core.esm.js'; -export { arrow, autoPlacement, detectOverflow, flip, hide, inline, limitShift, offset, shift, size } from './floating-ui.core.esm.js'; - -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) { - if (isNode(node)) { - return (node.nodeName || '').toLowerCase(); - } - // Mocked nodes in testing environments may not be instances of Node. By - // returning `#document` an infinite loop won't occur. - // https://github.com/floating-ui/floating-ui/issues/2317 - return '#document'; -} - -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; - } - return node instanceof getWindow(node).ShadowRoot || 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; -const floor = Math.floor; -const createEmptyCoords = v => ({ - x: v, - y: v -}); +import { rectToClientRect, arrow as arrow$1, autoPlacement as autoPlacement$1, detectOverflow as detectOverflow$1, flip as flip$1, hide as hide$1, inline as inline$1, limitShift as limitShift$1, offset as offset$1, shift as shift$1, size as size$1, computePosition as computePosition$1 } from './floating-ui.core.esm.js'; +import { round, createCoords, max, min, floor } from './floating-ui.utils.esm.js'; +import { getComputedStyle as getComputedStyle$1, isHTMLElement, isElement, getWindow, isWebKit, getFrameElement, getNodeScroll, getDocumentElement, isTopLayer, getNodeName, isOverflowElement, getOverflowAncestors, getParentNode, isLastTraversableNode, isContainingBlock, isTableElement, getContainingBlock } from './floating-ui.utils.dom.esm.js'; +export { getOverflowAncestors } from './floating-ui.utils.dom.esm.js'; function getCssDimensions(element) { const css = getComputedStyle$1(element); @@ -100,7 +31,7 @@ function unwrapElement(element) { function getScale(element) { const domElement = unwrapElement(element); if (!isHTMLElement(domElement)) { - return createEmptyCoords(1); + return createCoords(1); } const rect = domElement.getBoundingClientRect(); const { @@ -125,24 +56,26 @@ function getScale(element) { }; } -const noOffsets = /*#__PURE__*/createEmptyCoords(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) { +const noOffsets = /*#__PURE__*/createCoords(0); +function getVisualOffsets(element) { + const win = getWindow(element); + if (!isWebKit() || !win.visualViewport) { 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 + x: win.visualViewport.offsetLeft, + y: win.visualViewport.offsetTop }; } +function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { + if (isFixed === void 0) { + isFixed = false; + } + if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) { + return false; + } + return isFixed; +} function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { if (includeScale === void 0) { @@ -153,7 +86,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar } const clientRect = element.getBoundingClientRect(); const domElement = unwrapElement(element); - let scale = createEmptyCoords(1); + let scale = createCoords(1); if (includeScale) { if (offsetParent) { if (isElement(offsetParent)) { @@ -163,7 +96,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar scale = getScale(element); } } - const visualOffsets = getVisualOffsets(domElement, isFixedStrategy, offsetParent); + const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); let x = (clientRect.left + visualOffsets.x) / scale.x; let y = (clientRect.top + visualOffsets.y) / scale.y; let width = clientRect.width / scale.x; @@ -171,11 +104,12 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar if (domElement) { const win = getWindow(domElement); const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent; - let currentIFrame = win.frameElement; - while (currentIFrame && offsetParent && offsetWin !== win) { + let currentWin = win; + let currentIFrame = getFrameElement(currentWin); + while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -184,7 +118,8 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar height *= iframeScale.y; x += left; y += top; - currentIFrame = getWindow(currentIFrame).frameElement; + currentWin = getWindow(currentIFrame); + currentIFrame = getFrameElement(currentWin); } } return rectToClientRect({ @@ -195,41 +130,47 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar }); } -function getDocumentElement(node) { - return ((isNode(node) ? node.ownerDocument : node.document) || window.document).documentElement; +// If has a CSS width greater than the viewport, then this will be +// incorrect for RTL. +function getWindowScrollBarX(element, rect) { + const leftScroll = getNodeScroll(element).scrollLeft; + if (!rect) { + return getBoundingClientRect(getDocumentElement(element)).left + leftScroll; + } + return rect.left + leftScroll; } -function getNodeScroll(element) { - if (isElement(element)) { - return { - scrollLeft: element.scrollLeft, - scrollTop: element.scrollTop - }; - } +function getHTMLOffset(documentElement, scroll) { + const htmlRect = documentElement.getBoundingClientRect(); + const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect); + const y = htmlRect.top + scroll.scrollTop; return { - scrollLeft: element.pageXOffset, - scrollTop: element.pageYOffset + x, + y }; } function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { let { + elements, rect, offsetParent, strategy } = _ref; - const isOffsetParentAnElement = isHTMLElement(offsetParent); + const isFixed = strategy === 'fixed'; const documentElement = getDocumentElement(offsetParent); - if (offsetParent === documentElement) { + const topLayer = elements ? isTopLayer(elements.floating) : false; + if (offsetParent === documentElement || topLayer && isFixed) { return rect; } let scroll = { scrollLeft: 0, scrollTop: 0 }; - let scale = createEmptyCoords(1); - const offsets = createEmptyCoords(0); - if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') { + let scale = createCoords(1); + const offsets = createCoords(0); + const isOffsetParentAnElement = isHTMLElement(offsetParent); + if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) { scroll = getNodeScroll(offsetParent); } @@ -240,18 +181,17 @@ function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { offsets.y = offsetRect.y + offsetParent.clientTop; } } + const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); 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 + x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x, + y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.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; +function getClientRects(element) { + return Array.from(element.getClientRects()); } // Gets the entire size of the scrollable document area, even extending outside @@ -275,47 +215,10 @@ function getDocumentRect(element) { }; } -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)) { - return node.ownerDocument ? node.ownerDocument.body : node.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)); -} - +// Safety check: ensure the scrollbar space is reasonable in case this +// calculation is affected by unusual styles. +// Most scrollbars leave 15-18px of space. +const SCROLLBAR_MAX = 25; function getViewportRect(element, strategy) { const win = getWindow(element); const html = getDocumentElement(element); @@ -327,12 +230,30 @@ function getViewportRect(element, strategy) { if (visualViewport) { width = visualViewport.width; height = visualViewport.height; - const visualViewportBased = isSafari(); + const visualViewportBased = isWebKit(); if (!visualViewportBased || visualViewportBased && strategy === 'fixed') { x = visualViewport.offsetLeft; y = visualViewport.offsetTop; } } + const windowScrollbarX = getWindowScrollBarX(html); + // `overflow: hidden` + `scrollbar-gutter: stable` reduces the + // visual width of the but this is not considered in the size + // of `html.clientWidth`. + if (windowScrollbarX <= 0) { + const doc = html.ownerDocument; + const body = doc.body; + const bodyStyles = getComputedStyle(body); + const bodyMarginInline = doc.compatMode === 'CSS1Compat' ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0; + const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline); + if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) { + width -= clippingStableScrollbarWidth; + } + } else if (windowScrollbarX <= SCROLLBAR_MAX) { + // If the scrollbar is on the left, the width needs to be extended + // by the scrollbar amount so there isn't extra space on the right. + width += windowScrollbarX; + } return { width, height, @@ -341,12 +262,13 @@ function getViewportRect(element, strategy) { }; } +const absoluteOrFixed = /*#__PURE__*/new Set(['absolute', 'fixed']); // 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) : createEmptyCoords(1); + const scale = isHTMLElement(element) ? getScale(element) : createCoords(1); const width = element.clientWidth * scale.x; const height = element.clientHeight * scale.y; const x = left * scale.x; @@ -369,9 +291,10 @@ function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) } else { const visualOffsets = getVisualOffsets(element); rect = { - ...clippingAncestor, x: clippingAncestor.x - visualOffsets.x, - y: clippingAncestor.y - visualOffsets.y + y: clippingAncestor.y - visualOffsets.y, + width: clippingAncestor.width, + height: clippingAncestor.height }; } return rectToClientRect(rect); @@ -392,7 +315,7 @@ function getClippingElementAncestors(element, cache) { if (cachedResult) { return cachedResult; } - let result = getOverflowAncestors(element).filter(el => isElement(el) && getNodeName(el) !== 'body'); + let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; @@ -404,7 +327,7 @@ function getClippingElementAncestors(element, cache) { 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); + const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && absoluteOrFixed.has(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode); if (shouldDropCurrentNode) { // Drop non-containing blocks. result = result.filter(ancestor => ancestor !== currentNode); @@ -427,7 +350,7 @@ function getClippingRect(_ref) { rootBoundary, strategy } = _ref; - const elementClippingAncestors = boundary === 'clippingAncestors' ? getClippingElementAncestors(element, this._c) : [].concat(boundary); + const elementClippingAncestors = boundary === 'clippingAncestors' ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary); const clippingAncestors = [...elementClippingAncestors, rootBoundary]; const firstClippingAncestor = clippingAncestors[0]; const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => { @@ -447,45 +370,14 @@ function getClippingRect(_ref) { } 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; + const { + width, + height + } = getCssDimensions(element); + return { + width, + height + }; } function getRectRelativeToOffsetParent(element, offsetParent, strategy) { @@ -497,64 +389,134 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { scrollLeft: 0, scrollTop: 0 }; - const offsets = createEmptyCoords(0); + const offsets = createCoords(0); + + // If the scrollbar appears on the left (e.g. RTL systems). Use + // Firefox with layout.scrollbar.side = 3 in about:config to test this. + function setLeftRTLScrollbarOffset() { + offsets.x = getWindowScrollBarX(documentElement); + } if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) { scroll = getNodeScroll(offsetParent); } - if (isHTMLElement(offsetParent)) { + if (isOffsetParentAnElement) { 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); + setLeftRTLScrollbarOffset(); } } + if (isFixed && !isOffsetParentAnElement && documentElement) { + setLeftRTLScrollbarOffset(); + } + const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); + const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x; + const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y; return { - x: rect.left + scroll.scrollLeft - offsets.x, - y: rect.top + scroll.scrollTop - offsets.y, + x, + y, width: rect.width, height: rect.height }; } +function isStaticPositioned(element) { + return getComputedStyle$1(element).position === 'static'; +} + +function getTrueOffsetParent(element, polyfill) { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { + return null; + } + if (polyfill) { + return polyfill(element); + } + let rawOffsetParent = element.offsetParent; + + // Firefox returns the element as the offsetParent if it's non-static, + // while Chrome and Safari return the element. The element must + // be used to perform the correct calculations even if the element is + // non-static. + if (getDocumentElement(element) === rawOffsetParent) { + rawOffsetParent = rawOffsetParent.ownerDocument.body; + } + return rawOffsetParent; +} + +// Gets the closest ancestor positioned element. Handles some edge cases, +// such as table ancestors and cross browser bugs. +function getOffsetParent(element, polyfill) { + const win = getWindow(element); + if (isTopLayer(element)) { + return win; + } + if (!isHTMLElement(element)) { + let svgOffsetParent = getParentNode(element); + while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) { + if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) { + return svgOffsetParent; + } + svgOffsetParent = getParentNode(svgOffsetParent); + } + return win; + } + let offsetParent = getTrueOffsetParent(element, polyfill); + while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) { + offsetParent = getTrueOffsetParent(offsetParent, polyfill); + } + if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) { + return win; + } + return offsetParent || getContainingBlock(element) || win; +} + +const getElementRects = async function (data) { + const getOffsetParentFn = this.getOffsetParent || getOffsetParent; + const getDimensionsFn = this.getDimensions; + const floatingDimensions = await getDimensionsFn(data.floating); + return { + reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy), + floating: { + x: 0, + y: 0, + width: floatingDimensions.width, + height: floatingDimensions.height + } + }; +}; + +function isRTL(element) { + return getComputedStyle$1(element).direction === 'rtl'; +} + const platform = { - getClippingRect, convertOffsetParentRelativeRectToViewportRelativeRect, - isElement, - getDimensions, - getOffsetParent, getDocumentElement, + getClippingRect, + getOffsetParent, + getElementRects, + getClientRects, + getDimensions, 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' + isElement, + isRTL }; +function rectsAreEqual(a, b) { + return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height; +} + // https://samthor.au/2021/observing-dom/ function observeMove(element, onMove) { let io = null; let timeoutId; const root = getDocumentElement(element); function cleanup() { + var _io; clearTimeout(timeoutId); - io && io.disconnect(); + (_io = io) == null || _io.disconnect(); io = null; } function refresh(skip, threshold) { @@ -565,12 +527,13 @@ function observeMove(element, onMove) { threshold = 1; } cleanup(); + const elementRectForRootMargin = element.getBoundingClientRect(); const { left, top, width, height - } = element.getBoundingClientRect(); + } = elementRectForRootMargin; if (!skip) { onMove(); } @@ -582,26 +545,51 @@ function observeMove(element, onMove) { const insetBottom = floor(root.clientHeight - (top + height)); const insetLeft = floor(left); const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; + const options = { + rootMargin, + threshold: max(0, min(1, threshold)) || 1 + }; let isFirstUpdate = true; - io = new IntersectionObserver(entries => { + function handleObserve(entries) { const ratio = entries[0].intersectionRatio; if (ratio !== threshold) { if (!isFirstUpdate) { return refresh(); } - if (ratio === 0) { + if (!ratio) { + // If the reference is clipped, the ratio is 0. Throttle the refresh + // to prevent an infinite loop of updates. timeoutId = setTimeout(() => { refresh(false, 1e-7); - }, 100); + }, 1000); } else { refresh(false, ratio); } } + if (ratio === 1 && !rectsAreEqual(elementRectForRootMargin, element.getBoundingClientRect())) { + // It's possible that even though the ratio is reported as 1, the + // element is not actually fully within the IntersectionObserver's root + // area anymore. This can happen under performance constraints. This may + // be a bug in the browser's IntersectionObserver implementation. To + // work around this, we compare the element's bounding rect now with + // what it was at the time we created the IntersectionObserver. If they + // are not equal then the element moved, so we refresh. + refresh(); + } isFirstUpdate = false; - }, { - rootMargin, - threshold - }); + } + + // Older browsers don't support a `document` as the root and will throw an + // error. + try { + io = new IntersectionObserver(handleObserve, { + ...options, + // Handle