Refactor popover engine to use Floating UI

This commit is contained in:
thePR0M3TH3AN committed 2025-10-16 15:46:57 -04:00
1 parent 3b53cfd683
commit 43a75e86c8
8 files changed
+194 -232

No files matched your search

+12
View File
@@ -76,6 +76,18 @@ Pass `document`, `placement`, and other floating-ui options through the third
argument. The helper exposes `open()`, `close()`, `toggle()`, `isOpen()`, and argument. The helper exposes `open()`, `close()`, `toggle()`, `isOpen()`, and
`destroy()` for lifecycle control. `destroy()` for lifecycle control.
### Floating UI dependency
The shared popover engine standardizes on
[`@floating-ui/dom`](https://floating-ui.com/) for positioning. The module is
vendored to `vendor/floating-ui.dom.bundle.min.js` by
`npm run build:beacon`, which bundles dependencies and copies the upstream
license alongside `nostr-tools`. Run that build whenever you bump Floating UI so
the checked-in vendor artifact stays in sync. New overlays should lean on the
engine rather than re-implementing manual gap, flip, or viewport clamp math—the
middleware layer already applies the configured gap and viewport padding via
`offset()`, `flip()`, and `shift()` helpers.
## Tailwind overlay classes ## Tailwind overlay classes
Tailwind ships token-backed overlay helpers that keep surfaces aligned with the Tailwind ships token-backed overlay helpers that keep surfaces aligned with the
+94 -231
View File
@@ -5,6 +5,14 @@ import {
readDesignToken, readDesignToken,
} from "../../designSystem/metrics.js"; } from "../../designSystem/metrics.js";
import { ensureOverlayRoot } from "./overlayRoot.js"; import { ensureOverlayRoot } from "./overlayRoot.js";
import {
arrow as arrowMiddleware,
autoUpdate,
computePosition,
flip,
offset,
shift,
} from "../../../vendor/floating-ui.dom.bundle.min.js";
const DEFAULT_PLACEMENT = "bottom-start"; const DEFAULT_PLACEMENT = "bottom-start";
const DEFAULT_STRATEGY = "fixed"; const DEFAULT_STRATEGY = "fixed";
@@ -107,61 +115,56 @@ function resolveArrow(option, panel) {
return null; return null;
} }
function applyArrowStyles({ function applyArrowStyles({ arrowElement, placement, middlewareData }) {
arrowElement, if (!arrowElement || typeof placement !== "string") {
side,
panelLeft,
panelTop,
panelWidth,
panelHeight,
anchorRect,
}) {
if (!arrowElement || !side || !anchorRect) {
return; return;
} }
const arrowRect = const basePlacement = placement.split("-")[0];
typeof arrowElement.getBoundingClientRect === "function" const arrowData = middlewareData?.arrow || null;
? arrowElement.getBoundingClientRect()
: null;
const arrowWidth = arrowRect?.width ?? arrowElement.offsetWidth ?? 0;
const arrowHeight = arrowRect?.height ?? arrowElement.offsetHeight ?? 0;
const anchorCenterX = anchorRect.left + anchorRect.width / 2;
const anchorCenterY = anchorRect.top + anchorRect.height / 2;
arrowElement.style.left = ""; arrowElement.style.left = "";
arrowElement.style.right = ""; arrowElement.style.right = "";
arrowElement.style.top = ""; arrowElement.style.top = "";
arrowElement.style.bottom = ""; arrowElement.style.bottom = "";
if (side === "top" || side === "bottom") { if (!arrowData || !basePlacement) {
let arrowLeft = anchorCenterX - panelLeft - arrowWidth / 2; delete arrowElement.dataset.popoverArrowSide;
arrowLeft = Math.min( return;
Math.max(arrowLeft, 0),
Math.max(panelWidth - arrowWidth, 0),
);
arrowElement.style.left = `${Math.round(arrowLeft)}px`;
if (side === "bottom") {
arrowElement.style.top = `${-Math.round(arrowHeight)}px`;
} else {
arrowElement.style.bottom = `${-Math.round(arrowHeight)}px`;
}
} else if (side === "left" || side === "right") {
let arrowTop = anchorCenterY - panelTop - arrowHeight / 2;
arrowTop = Math.min(
Math.max(arrowTop, 0),
Math.max(panelHeight - arrowHeight, 0),
);
arrowElement.style.top = `${Math.round(arrowTop)}px`;
if (side === "right") {
arrowElement.style.left = `${-Math.round(arrowWidth)}px`;
} else {
arrowElement.style.right = `${-Math.round(arrowWidth)}px`;
}
} }
arrowElement.dataset.popoverArrowSide = side; const staticSideMap = {
top: "bottom",
right: "left",
bottom: "top",
left: "right",
};
if (Number.isFinite(arrowData.x)) {
arrowElement.style.left = `${Math.round(arrowData.x)}px`;
}
if (Number.isFinite(arrowData.y)) {
arrowElement.style.top = `${Math.round(arrowData.y)}px`;
}
const staticSide = staticSideMap[basePlacement] || null;
if (staticSide) {
const arrowRect =
typeof arrowElement.getBoundingClientRect === "function"
? arrowElement.getBoundingClientRect()
: null;
const arrowWidth = arrowRect?.width ?? arrowElement.offsetWidth ?? 0;
const arrowHeight = arrowRect?.height ?? arrowElement.offsetHeight ?? 0;
const offsetValue =
basePlacement === "top" || basePlacement === "bottom"
? arrowHeight
: arrowWidth;
arrowElement.style[staticSide] = `${-Math.round(offsetValue)}px`;
}
arrowElement.dataset.popoverArrowSide = basePlacement;
} }
function containsTarget(root, target) { function containsTarget(root, target) {
@@ -181,85 +184,6 @@ function setExpandedAttribute(trigger, value) {
trigger.setAttribute("aria-expanded", value ? "true" : "false"); trigger.setAttribute("aria-expanded", value ? "true" : "false");
} }
function createAutoUpdate({ documentRef, anchor, panel, update }) {
if (!documentRef || !anchor || !panel || typeof update !== "function") {
return null;
}
const cleanupFns = [];
const safeUpdate = () => {
try {
const result = update();
if (result && typeof result.then === "function") {
result.catch((error) => {
logger.dev.warn("[popover] auto-update failed", error);
});
}
} catch (error) {
logger.dev.warn("[popover] auto-update failed", error);
}
};
const view = documentRef.defaultView || globalThis;
if (view && typeof view.addEventListener === "function") {
const resizeHandler = () => safeUpdate();
view.addEventListener("resize", resizeHandler);
cleanupFns.push(() => view.removeEventListener("resize", resizeHandler));
const scrollHandler = () => safeUpdate();
view.addEventListener("scroll", scrollHandler, true);
cleanupFns.push(() => view.removeEventListener("scroll", scrollHandler, true));
}
const scrollTarget = documentRef.scrollingElement || documentRef;
if (scrollTarget && typeof scrollTarget.addEventListener === "function") {
const docScrollHandler = () => safeUpdate();
scrollTarget.addEventListener("scroll", docScrollHandler, true);
cleanupFns.push(() =>
scrollTarget.removeEventListener("scroll", docScrollHandler, true),
);
}
const observers = [];
if (typeof ResizeObserver === "function") {
try {
const anchorObserver = new ResizeObserver(safeUpdate);
anchorObserver.observe(anchor);
observers.push(anchorObserver);
} catch (error) {
logger.dev.warn("[popover] anchor resize observer failed", error);
}
if (panel !== anchor) {
try {
const panelObserver = new ResizeObserver(safeUpdate);
panelObserver.observe(panel);
observers.push(panelObserver);
} catch (error) {
logger.dev.warn("[popover] panel resize observer failed", error);
}
}
}
return () => {
cleanupFns.forEach((cleanup) => {
try {
cleanup();
} catch (error) {
logger.dev.warn("[popover] auto-update cleanup failed", error);
}
});
observers.forEach((observer) => {
try {
observer.disconnect();
} catch (error) {
logger.dev.warn("[popover] observer disconnect failed", error);
}
});
};
}
export function createPopover(trigger, render, options = {}) { export function createPopover(trigger, render, options = {}) {
const anchor = isElement(trigger) ? trigger : null; const anchor = isElement(trigger) ? trigger : null;
const documentRef = resolveDocument(anchor, options.document); const documentRef = resolveDocument(anchor, options.document);
@@ -714,124 +638,56 @@ export function createPopover(trigger, render, options = {}) {
} }
async function updatePosition() { async function updatePosition() {
if (!anchor || !panel || !documentRef) { if (!anchor || !panel) {
return; return;
} }
try { try {
const anchorRect =
typeof anchor.getBoundingClientRect === "function"
? anchor.getBoundingClientRect()
: null;
if (!anchorRect) {
return;
}
const view = documentRef.defaultView || globalThis;
const docEl = documentRef.documentElement || null;
const viewportWidth =
docEl?.clientWidth ?? view?.innerWidth ?? panel.offsetWidth ?? 0;
const viewportHeight =
docEl?.clientHeight ?? view?.innerHeight ?? panel.offsetHeight ?? 0;
const panelRect =
typeof panel.getBoundingClientRect === "function"
? panel.getBoundingClientRect()
: null;
const panelWidth = panelRect?.width ?? panel.offsetWidth ?? 0;
const panelHeight = panelRect?.height ?? panel.offsetHeight ?? 0;
let [side, alignment = "start"] = placement.split("-");
if (!side || !["top", "bottom", "left", "right"].includes(side)) {
side = "bottom";
}
if (!["start", "end", "center"].includes(alignment)) {
alignment = "start";
}
const effectiveGap = Number.isFinite(gap) ? gap : 0; const effectiveGap = Number.isFinite(gap) ? gap : 0;
const safePadding = Number.isFinite(viewportPadding) ? viewportPadding : 0; const safePadding = Number.isFinite(viewportPadding) ? viewportPadding : 0;
const middleware = [
offset(effectiveGap),
flip({ padding: safePadding }),
shift({ padding: safePadding }),
];
function clamp(value, min, max, fallback) { if (arrowElement) {
if (!Number.isFinite(value)) { middleware.push(
return fallback ?? min; arrowMiddleware({
} element: arrowElement,
if (min > max) { }),
return fallback ?? (min + max) / 2; );
}
return Math.min(Math.max(value, min), max);
} }
if (side === "bottom") { const {
const fitsBelow = x,
anchorRect.bottom + effectiveGap + panelHeight <= y,
viewportHeight - safePadding; placement: resolvedPlacement,
const fitsAbove = middlewareData,
anchorRect.top - effectiveGap - panelHeight >= safePadding; strategy: resolvedStrategy,
if (!fitsBelow && fitsAbove) { } = await computePosition(anchor, panel, {
side = "top"; placement,
} strategy,
} else if (side === "top") { middleware,
const fitsAbove = });
anchorRect.top - effectiveGap - panelHeight >= safePadding;
const fitsBelow =
anchorRect.bottom + effectiveGap + panelHeight <=
viewportHeight - safePadding;
if (!fitsAbove && fitsBelow) {
side = "bottom";
}
}
let top = 0; const nextX = Number.isFinite(x) ? x : 0;
let left = 0; const nextY = Number.isFinite(y) ? y : 0;
const finalPlacement =
typeof resolvedPlacement === "string" && resolvedPlacement
? resolvedPlacement
: placement;
if (side === "top") { panel.style.position = resolvedStrategy || strategy;
top = anchorRect.top - panelHeight - effectiveGap; panel.style.left = `${Math.round(nextX)}px`;
} else if (side === "bottom") { panel.style.top = `${Math.round(nextY)}px`;
top = anchorRect.bottom + effectiveGap; panel.dataset.popoverPlacement = finalPlacement;
} else if (side === "left") {
top = anchorRect.top + anchorRect.height / 2 - panelHeight / 2;
} else if (side === "right") {
top = anchorRect.top + anchorRect.height / 2 - panelHeight / 2;
}
if (side === "left") {
left = anchorRect.left - panelWidth - effectiveGap;
} else if (side === "right") {
left = anchorRect.right + effectiveGap;
} else if (alignment === "end") {
left = anchorRect.right - panelWidth;
} else if (alignment === "center") {
left = anchorRect.left + anchorRect.width / 2 - panelWidth / 2;
} else {
left = anchorRect.left;
}
const minTop = safePadding;
const maxTop = viewportHeight - safePadding - panelHeight;
const minLeft = safePadding;
const maxLeft = viewportWidth - safePadding - panelWidth;
const fallbackTop = viewportHeight / 2 - panelHeight / 2;
const fallbackLeft = viewportWidth / 2 - panelWidth / 2;
top = clamp(top, minTop, maxTop, fallbackTop);
left = clamp(left, minLeft, maxLeft, fallbackLeft);
panel.style.position = strategy;
panel.style.left = `${Math.round(left)}px`;
panel.style.top = `${Math.round(top)}px`;
panel.dataset.popoverPlacement = `${side}-${alignment}`;
if (arrowElement) { if (arrowElement) {
applyArrowStyles({ applyArrowStyles({
arrowElement, arrowElement,
side, placement: finalPlacement,
panelLeft: left, middlewareData,
panelTop: top,
panelWidth,
panelHeight,
anchorRect,
}); });
} }
} catch (error) { } catch (error) {
@@ -909,12 +765,19 @@ export function createPopover(trigger, render, options = {}) {
autoUpdateCleanup = null; autoUpdateCleanup = null;
} }
autoUpdateCleanup = createAutoUpdate({ autoUpdateCleanup = autoUpdate(
documentRef,
anchor, anchor,
panel: panelElement, panelElement,
update: () => updatePosition(), () => {
}); void updatePosition();
},
{
ancestorScroll: true,
ancestorResize: true,
elementResize: true,
layoutShift: true,
},
);
await updatePosition(); await updatePosition();
+26
View File
@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.7.4",
"path": "^0.12.7", "path": "^0.12.7",
"webtorrent": "^1.9.7" "webtorrent": "^1.9.7"
}, },
@@ -708,6 +709,31 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+1
View File
@@ -40,6 +40,7 @@
}, },
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.7.4",
"path": "^0.12.7", "path": "^0.12.7",
"webtorrent": "^1.9.7" "webtorrent": "^1.9.7"
} }
+32 -1
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
import { build } from "esbuild"; import { build } from "esbuild";
import { mkdir } from "node:fs/promises"; import { copyFile, mkdir } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { createRequire } from "node:module";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@@ -11,8 +12,23 @@ const repoRoot = path.resolve(__dirname, "..");
const entryPoint = path.join(repoRoot, "torrent/src/beacon-vendor.js"); const entryPoint = path.join(repoRoot, "torrent/src/beacon-vendor.js");
const outDir = path.join(repoRoot, "torrent/dist"); const outDir = path.join(repoRoot, "torrent/dist");
const outFile = path.join(outDir, "beacon.js"); const outFile = path.join(outDir, "beacon.js");
const vendorDir = path.join(repoRoot, "vendor");
const vendorOutFile = path.join(vendorDir, "floating-ui.dom.bundle.min.js");
const vendorLicenseFile = path.join(vendorDir, "floating-ui.LICENSE");
const floatingUiEntry = path.join(
repoRoot,
"scripts/vendor/floating-ui-dom-entry.mjs",
);
const require = createRequire(import.meta.url);
const floatingUiPackageJson = require.resolve("@floating-ui/dom/package.json");
const floatingUiLicense = path.join(
path.dirname(floatingUiPackageJson),
"LICENSE",
);
await mkdir(outDir, { recursive: true }); await mkdir(outDir, { recursive: true });
await mkdir(vendorDir, { recursive: true });
await build({ await build({
bundle: true, bundle: true,
@@ -34,3 +50,18 @@ await build({
}, },
logLevel: "info", logLevel: "info",
}); });
await build({
bundle: true,
entryPoints: [floatingUiEntry],
outfile: vendorOutFile,
format: "esm",
splitting: false,
platform: "browser",
target: ["es2020"],
minify: true,
sourcemap: false,
logLevel: "info",
});
await copyFile(floatingUiLicense, vendorLicenseFile);
+8
View File
@@ -0,0 +1,8 @@
export {
arrow,
autoUpdate,
computePosition,
flip,
offset,
shift,
} from "@floating-ui/dom";
+20
View File
@@ -0,0 +1,20 @@
MIT License
Copyright (c) 2021-present Floating UI contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+1
View File
@@ -0,0 +1 @@
var H=Math.min,B=Math.max,ot=Math.round,it=Math.floor,L=t=>({x:t,y:t}),qt={left:"right",right:"left",bottom:"top",top:"bottom"},Ut={start:"end",end:"start"};function lt(t,e,n){return B(t,H(e,n))}function U(t,e){return typeof t=="function"?t(e):t}function V(t){return t.split("-")[0]}function K(t){return t.split("-")[1]}function pt(t){return t==="x"?"y":"x"}function at(t){return t==="y"?"height":"width"}var Kt=new Set(["top","bottom"]);function W(t){return Kt.has(V(t))?"y":"x"}function ft(t){return pt(W(t))}function Ot(t,e,n){n===void 0&&(n=!1);let o=K(t),i=ft(t),r=at(i),s=i==="x"?o===(n?"end":"start")?"right":"left":o==="start"?"bottom":"top";return e.reference[r]>e.floating[r]&&(s=nt(s)),[s,nt(s)]}function Rt(t){let e=nt(t);return[ct(t),e,ct(e)]}function ct(t){return t.replace(/start|end/g,e=>Ut[e])}var bt=["left","right"],At=["right","left"],Gt=["top","bottom"],Jt=["bottom","top"];function Qt(t,e,n){switch(t){case"top":case"bottom":return n?e?At:bt:e?bt:At;case"left":case"right":return e?Gt:Jt;default:return[]}}function St(t,e,n,o){let i=K(t),r=Qt(V(t),n==="start",o);return i&&(r=r.map(s=>s+"-"+i),e&&(r=r.concat(r.map(ct)))),r}function nt(t){return t.replace(/left|right|bottom|top/g,e=>qt[e])}function Zt(t){return{top:0,right:0,bottom:0,left:0,...t}}function wt(t){return typeof t!="number"?Zt(t):{top:t,right:t,bottom:t,left:t}}function X(t){let{x:e,y:n,width:o,height:i}=t;return{width:o,height:i,top:n,left:e,right:e+o,bottom:n+i,x:e,y:n}}function Ct(t,e,n){let{reference:o,floating:i}=t,r=W(e),s=ft(e),c=at(s),a=V(e),l=r==="y",d=o.x+o.width/2-i.width/2,u=o.y+o.height/2-i.height/2,g=o[c]/2-i[c]/2,f;switch(a){case"top":f={x:d,y:o.y-i.height};break;case"bottom":f={x:d,y:o.y+o.height};break;case"right":f={x:o.x+o.width,y:u};break;case"left":f={x:o.x-i.width,y:u};break;default:f={x:o.x,y:o.y}}switch(K(e)){case"start":f[s]-=g*(n&&l?-1:1);break;case"end":f[s]+=g*(n&&l?-1:1);break}return f}var Pt=async(t,e,n)=>{let{placement:o="bottom",strategy:i="absolute",middleware:r=[],platform:s}=n,c=r.filter(Boolean),a=await(s.isRTL==null?void 0:s.isRTL(e)),l=await s.getElementRects({reference:t,floating:e,strategy:i}),{x:d,y:u}=Ct(l,o,a),g=o,f={},m=0;for(let h=0;h<c.length;h++){let{name:w,fn:p}=c[h],{x,y,data:b,reset:v}=await p({x:d,y:u,initialPlacement:o,placement:g,strategy:i,middlewareData:f,rects:l,platform:s,elements:{reference:t,floating:e}});d=x??d,u=y??u,f={...f,[w]:{...f[w],...b}},v&&m<=50&&(m++,typeof v=="object"&&(v.placement&&(g=v.placement),v.rects&&(l=v.rects===!0?await s.getElementRects({reference:t,floating:e,strategy:i}):v.rects),{x:d,y:u}=Ct(l,g,a)),h=-1)}return{x:d,y:u,placement:g,strategy:i,middlewareData:f}};async function xt(t,e){var n;e===void 0&&(e={});let{x:o,y:i,platform:r,rects:s,elements:c,strategy:a}=t,{boundary:l="clippingAncestors",rootBoundary:d="viewport",elementContext:u="floating",altBoundary:g=!1,padding:f=0}=U(e,t),m=wt(f),w=c[g?u==="floating"?"reference":"floating":u],p=X(await r.getClippingRect({element:(n=await(r.isElement==null?void 0:r.isElement(w)))==null||n?w:w.contextElement||await(r.getDocumentElement==null?void 0:r.getDocumentElement(c.floating)),boundary:l,rootBoundary:d,strategy:a})),x=u==="floating"?{x:o,y:i,width:s.floating.width,height:s.floating.height}:s.reference,y=await(r.getOffsetParent==null?void 0:r.getOffsetParent(c.floating)),b=await(r.isElement==null?void 0:r.isElement(y))?await(r.getScale==null?void 0:r.getScale(y))||{x:1,y:1}:{x:1,y:1},v=X(r.convertOffsetParentRelativeRectToViewportRelativeRect?await r.convertOffsetParentRelativeRectToViewportRelativeRect({elements:c,rect:x,offsetParent:y,strategy:a}):x);return{top:(p.top-v.top+m.top)/b.y,bottom:(v.bottom-p.bottom+m.bottom)/b.y,left:(p.left-v.left+m.left)/b.x,right:(v.right-p.right+m.right)/b.x}}var Tt=t=>({name:"arrow",options:t,async fn(e){let{x:n,y:o,placement:i,rects:r,platform:s,elements:c,middlewareData:a}=e,{element:l,padding:d=0}=U(t,e)||{};if(l==null)return{};let u=wt(d),g={x:n,y:o},f=ft(i),m=at(f),h=await s.getDimensions(l),w=f==="y",p=w?"top":"left",x=w?"bottom":"right",y=w?"clientHeight":"clientWidth",b=r.reference[m]+r.reference[f]-g[f]-r.floating[m],v=g[f]-r.reference[f],M=await(s.getOffsetParent==null?void 0:s.getOffsetParent(l)),R=M?M[y]:0;(!R||!await(s.isElement==null?void 0:s.isElement(M)))&&(R=c.floating[y]||r.floating[m]);let Z=b/2-v/2,_=R/2-h[m]/2-1,F=H(u[p],_),tt=H(u[x],_),z=F,et=R-h[m]-tt,A=R/2-h[m]/2+Z,I=lt(z,A,et),k=!a.arrow&&K(i)!=null&&A!==I&&r.reference[m]/2-(A<z?F:tt)-h[m]/2<0,P=k?A<z?A-z:A-et:0;return{[f]:g[f]+P,data:{[f]:I,centerOffset:A-I-P,...k&&{alignmentOffset:P}},reset:k}}});var Lt=function(t){return t===void 0&&(t={}),{name:"flip",options:t,async fn(e){var n,o;let{placement:i,middlewareData:r,rects:s,initialPlacement:c,platform:a,elements:l}=e,{mainAxis:d=!0,crossAxis:u=!0,fallbackPlacements:g,fallbackStrategy:f="bestFit",fallbackAxisSideDirection:m="none",flipAlignment:h=!0,...w}=U(t,e);if((n=r.arrow)!=null&&n.alignmentOffset)return{};let p=V(i),x=W(c),y=V(c)===c,b=await(a.iLine truncated