From 43a75e86c8f2a3c77c66592aba84fecf4149aa53 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:46:57 -0400 Subject: [PATCH] Refactor popover engine to use Floating UI --- docs/menus.md | 12 + js/ui/overlay/popoverEngine.js | 325 +++++++---------------- package-lock.json | 26 ++ package.json | 1 + scripts/build-beacon.mjs | 33 ++- scripts/vendor/floating-ui-dom-entry.mjs | 8 + vendor/floating-ui.LICENSE | 20 ++ vendor/floating-ui.dom.bundle.min.js | 1 + 8 files changed, 194 insertions(+), 232 deletions(-) create mode 100644 scripts/vendor/floating-ui-dom-entry.mjs create mode 100644 vendor/floating-ui.LICENSE create mode 100644 vendor/floating-ui.dom.bundle.min.js diff --git a/docs/menus.md b/docs/menus.md index d9db32f8..e66c1213 100644 --- a/docs/menus.md +++ b/docs/menus.md @@ -76,6 +76,18 @@ Pass `document`, `placement`, and other floating-ui options through the third argument. The helper exposes `open()`, `close()`, `toggle()`, `isOpen()`, and `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 ships token-backed overlay helpers that keep surfaces aligned with the diff --git a/js/ui/overlay/popoverEngine.js b/js/ui/overlay/popoverEngine.js index 6ecfc1e5..494d3961 100644 --- a/js/ui/overlay/popoverEngine.js +++ b/js/ui/overlay/popoverEngine.js @@ -5,6 +5,14 @@ import { readDesignToken, } from "../../designSystem/metrics.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_STRATEGY = "fixed"; @@ -107,61 +115,56 @@ function resolveArrow(option, panel) { return null; } -function applyArrowStyles({ - arrowElement, - side, - panelLeft, - panelTop, - panelWidth, - panelHeight, - anchorRect, -}) { - if (!arrowElement || !side || !anchorRect) { +function applyArrowStyles({ arrowElement, placement, middlewareData }) { + if (!arrowElement || typeof placement !== "string") { return; } - const arrowRect = - typeof arrowElement.getBoundingClientRect === "function" - ? 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; + const basePlacement = placement.split("-")[0]; + const arrowData = middlewareData?.arrow || null; arrowElement.style.left = ""; arrowElement.style.right = ""; arrowElement.style.top = ""; arrowElement.style.bottom = ""; - if (side === "top" || side === "bottom") { - let arrowLeft = anchorCenterX - panelLeft - arrowWidth / 2; - arrowLeft = Math.min( - 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`; - } + if (!arrowData || !basePlacement) { + delete arrowElement.dataset.popoverArrowSide; + return; } - 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) { @@ -181,85 +184,6 @@ function setExpandedAttribute(trigger, value) { 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 = {}) { const anchor = isElement(trigger) ? trigger : null; const documentRef = resolveDocument(anchor, options.document); @@ -714,124 +638,56 @@ export function createPopover(trigger, render, options = {}) { } async function updatePosition() { - if (!anchor || !panel || !documentRef) { + if (!anchor || !panel) { return; } 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 safePadding = Number.isFinite(viewportPadding) ? viewportPadding : 0; + const middleware = [ + offset(effectiveGap), + flip({ padding: safePadding }), + shift({ padding: safePadding }), + ]; - function clamp(value, min, max, fallback) { - if (!Number.isFinite(value)) { - return fallback ?? min; - } - if (min > max) { - return fallback ?? (min + max) / 2; - } - return Math.min(Math.max(value, min), max); + if (arrowElement) { + middleware.push( + arrowMiddleware({ + element: arrowElement, + }), + ); } - if (side === "bottom") { - const fitsBelow = - anchorRect.bottom + effectiveGap + panelHeight <= - viewportHeight - safePadding; - const fitsAbove = - anchorRect.top - effectiveGap - panelHeight >= safePadding; - if (!fitsBelow && fitsAbove) { - side = "top"; - } - } else if (side === "top") { - const fitsAbove = - anchorRect.top - effectiveGap - panelHeight >= safePadding; - const fitsBelow = - anchorRect.bottom + effectiveGap + panelHeight <= - viewportHeight - safePadding; - if (!fitsAbove && fitsBelow) { - side = "bottom"; - } - } + const { + x, + y, + placement: resolvedPlacement, + middlewareData, + strategy: resolvedStrategy, + } = await computePosition(anchor, panel, { + placement, + strategy, + middleware, + }); - let top = 0; - let left = 0; + const nextX = Number.isFinite(x) ? x : 0; + const nextY = Number.isFinite(y) ? y : 0; + const finalPlacement = + typeof resolvedPlacement === "string" && resolvedPlacement + ? resolvedPlacement + : placement; - if (side === "top") { - top = anchorRect.top - panelHeight - effectiveGap; - } else if (side === "bottom") { - top = anchorRect.bottom + effectiveGap; - } 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}`; + panel.style.position = resolvedStrategy || strategy; + panel.style.left = `${Math.round(nextX)}px`; + panel.style.top = `${Math.round(nextY)}px`; + panel.dataset.popoverPlacement = finalPlacement; if (arrowElement) { applyArrowStyles({ arrowElement, - side, - panelLeft: left, - panelTop: top, - panelWidth, - panelHeight, - anchorRect, + placement: finalPlacement, + middlewareData, }); } } catch (error) { @@ -909,12 +765,19 @@ export function createPopover(trigger, render, options = {}) { autoUpdateCleanup = null; } - autoUpdateCleanup = createAutoUpdate({ - documentRef, + autoUpdateCleanup = autoUpdate( anchor, - panel: panelElement, - update: () => updatePosition(), - }); + panelElement, + () => { + void updatePosition(); + }, + { + ancestorScroll: true, + ancestorResize: true, + elementResize: true, + layoutShift: true, + }, + ); await updatePosition(); diff --git a/package-lock.json b/package-lock.json index 1c47cccc..203386f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "dependencies": { + "@floating-ui/dom": "^1.7.4", "path": "^0.12.7", "webtorrent": "^1.9.7" }, @@ -708,6 +709,31 @@ "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": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", diff --git a/package.json b/package.json index f2f92d88..f745fbb8 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "license": "GPL-3.0-or-later", "dependencies": { + "@floating-ui/dom": "^1.7.4", "path": "^0.12.7", "webtorrent": "^1.9.7" } diff --git a/scripts/build-beacon.mjs b/scripts/build-beacon.mjs index b85fd20a..39dbb3bd 100755 --- a/scripts/build-beacon.mjs +++ b/scripts/build-beacon.mjs @@ -1,9 +1,10 @@ #!/usr/bin/env node import { build } from "esbuild"; -import { mkdir } from "node:fs/promises"; +import { copyFile, mkdir } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { createRequire } from "node:module"; const __filename = fileURLToPath(import.meta.url); 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 outDir = path.join(repoRoot, "torrent/dist"); 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(vendorDir, { recursive: true }); await build({ bundle: true, @@ -34,3 +50,18 @@ await build({ }, 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); diff --git a/scripts/vendor/floating-ui-dom-entry.mjs b/scripts/vendor/floating-ui-dom-entry.mjs new file mode 100644 index 00000000..aa475ca0 --- /dev/null +++ b/scripts/vendor/floating-ui-dom-entry.mjs @@ -0,0 +1,8 @@ +export { + arrow, + autoUpdate, + computePosition, + flip, + offset, + shift, +} from "@floating-ui/dom"; diff --git a/vendor/floating-ui.LICENSE b/vendor/floating-ui.LICENSE new file mode 100644 index 00000000..639cdc6c --- /dev/null +++ b/vendor/floating-ui.LICENSE @@ -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. diff --git a/vendor/floating-ui.dom.bundle.min.js b/vendor/floating-ui.dom.bundle.min.js new file mode 100644 index 00000000..41865e7e --- /dev/null +++ b/vendor/floating-ui.dom.bundle.min.js @@ -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({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-(AA<=0)){var tt,z;let A=(((tt=r.flip)==null?void 0:tt.index)||0)+1,I=R[A];if(I&&(!(u==="alignment"?x!==W(I):!1)||F.every(T=>W(T.placement)===x?T.overflows[0]>0:!0)))return{data:{index:A,overflows:F},reset:{placement:I}};let k=(z=F.filter(P=>P.overflows[0]<=0).sort((P,T)=>P.overflows[1]-T.overflows[1])[0])==null?void 0:z.placement;if(!k)switch(f){case"bestFit":{var et;let P=(et=F.filter(T=>{if(M){let $=W(T.placement);return $===x||$==="y"}return!0}).map(T=>[T.placement,T.overflows.filter($=>$>0).reduce(($,Yt)=>$+Yt,0)]).sort((T,$)=>T[1]-$[1])[0])==null?void 0:et[0];P&&(k=P);break}case"initialPlacement":k=c;break}if(i!==k)return{reset:{placement:k}}}return{}}}};var te=new Set(["left","top"]);async function ee(t,e){let{placement:n,platform:o,elements:i}=t,r=await(o.isRTL==null?void 0:o.isRTL(i.floating)),s=V(n),c=K(n),a=W(n)==="y",l=te.has(s)?-1:1,d=r&&a?-1:1,u=U(e,t),{mainAxis:g,crossAxis:f,alignmentAxis:m}=typeof u=="number"?{mainAxis:u,crossAxis:0,alignmentAxis:null}:{mainAxis:u.mainAxis||0,crossAxis:u.crossAxis||0,alignmentAxis:u.alignmentAxis};return c&&typeof m=="number"&&(f=c==="end"?m*-1:m),a?{x:f*d,y:g*l}:{x:g*l,y:f*d}}var Et=function(t){return t===void 0&&(t=0),{name:"offset",options:t,async fn(e){var n,o;let{x:i,y:r,placement:s,middlewareData:c}=e,a=await ee(e,t);return s===((n=c.offset)==null?void 0:n.placement)&&(o=c.arrow)!=null&&o.alignmentOffset?{}:{x:i+a.x,y:r+a.y,data:{...a,placement:s}}}}},Dt=function(t){return t===void 0&&(t={}),{name:"shift",options:t,async fn(e){let{x:n,y:o,placement:i}=e,{mainAxis:r=!0,crossAxis:s=!1,limiter:c={fn:w=>{let{x:p,y:x}=w;return{x:p,y:x}}},...a}=U(t,e),l={x:n,y:o},d=await xt(e,a),u=W(V(i)),g=pt(u),f=l[g],m=l[u];if(r){let w=g==="y"?"top":"left",p=g==="y"?"bottom":"right",x=f+d[w],y=f-d[p];f=lt(x,f,y)}if(s){let w=u==="y"?"top":"left",p=u==="y"?"bottom":"right",x=m+d[w],y=m-d[p];m=lt(x,m,y)}let h=c.fn({...e,[g]:f,[u]:m});return{...h,data:{x:h.x-n,y:h.y-o,enabled:{[g]:r,[u]:s}}}}}};function ut(){return typeof window<"u"}function j(t){return Ft(t)?(t.nodeName||"").toLowerCase():"#document"}function O(t){var e;return(t==null||(e=t.ownerDocument)==null?void 0:e.defaultView)||window}function E(t){var e;return(e=(Ft(t)?t.ownerDocument:t.document)||window.document)==null?void 0:e.documentElement}function Ft(t){return ut()?t instanceof Node||t instanceof O(t).Node:!1}function S(t){return ut()?t instanceof Element||t instanceof O(t).Element:!1}function D(t){return ut()?t instanceof HTMLElement||t instanceof O(t).HTMLElement:!1}function Mt(t){return!ut()||typeof ShadowRoot>"u"?!1:t instanceof ShadowRoot||t instanceof O(t).ShadowRoot}var ne=new Set(["inline","contents"]);function J(t){let{overflow:e,overflowX:n,overflowY:o,display:i}=C(t);return/auto|scroll|overlay|hidden|clip/.test(e+o+n)&&!ne.has(i)}var oe=new Set(["table","td","th"]);function kt(t){return oe.has(j(t))}var ie=[":popover-open",":modal"];function st(t){return ie.some(e=>{try{return t.matches(e)}catch{return!1}})}var se=["transform","translate","scale","rotate","perspective"],re=["transform","translate","scale","rotate","perspective","filter"],ce=["paint","layout","strict","content"];function dt(t){let e=mt(),n=S(t)?C(t):t;return se.some(o=>n[o]?n[o]!=="none":!1)||(n.containerType?n.containerType!=="normal":!1)||!e&&(n.backdropFilter?n.backdropFilter!=="none":!1)||!e&&(n.filter?n.filter!=="none":!1)||re.some(o=>(n.willChange||"").includes(o))||ce.some(o=>(n.contain||"").includes(o))}function Bt(t){let e=N(t);for(;D(e)&&!Y(e);){if(dt(e))return e;if(st(e))return null;e=N(e)}return null}function mt(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}var le=new Set(["html","body","#document"]);function Y(t){return le.has(j(t))}function C(t){return O(t).getComputedStyle(t)}function rt(t){return S(t)?{scrollLeft:t.scrollLeft,scrollTop:t.scrollTop}:{scrollLeft:t.scrollX,scrollTop:t.scrollY}}function N(t){if(j(t)==="html")return t;let e=t.assignedSlot||t.parentNode||Mt(t)&&t.host||E(t);return Mt(e)?e.host:e}function Wt(t){let e=N(t);return Y(e)?t.ownerDocument?t.ownerDocument.body:t.body:D(e)&&J(e)?e:Wt(e)}function G(t,e,n){var o;e===void 0&&(e=[]),n===void 0&&(n=!0);let i=Wt(t),r=i===((o=t.ownerDocument)==null?void 0:o.body),s=O(i);if(r){let c=gt(s);return e.concat(s,s.visualViewport||[],J(i)?i:[],c&&n?G(c):[])}return e.concat(i,G(i,[],n))}function gt(t){return t.parent&&Object.getPrototypeOf(t.parent)?t.frameElement:null}function Vt(t){let e=C(t),n=parseFloat(e.width)||0,o=parseFloat(e.height)||0,i=D(t),r=i?t.offsetWidth:n,s=i?t.offsetHeight:o,c=ot(n)!==r||ot(o)!==s;return c&&(n=r,o=s),{width:n,height:o,$:c}}function vt(t){return S(t)?t:t.contextElement}function Q(t){let e=vt(t);if(!D(e))return L(1);let n=e.getBoundingClientRect(),{width:o,height:i,$:r}=Vt(e),s=(r?ot(n.width):n.width)/o,c=(r?ot(n.height):n.height)/i;return(!s||!Number.isFinite(s))&&(s=1),(!c||!Number.isFinite(c))&&(c=1),{x:s,y:c}}var ae=L(0);function _t(t){let e=O(t);return!mt()||!e.visualViewport?ae:{x:e.visualViewport.offsetLeft,y:e.visualViewport.offsetTop}}function fe(t,e,n){return e===void 0&&(e=!1),!n||e&&n!==O(t)?!1:e}function q(t,e,n,o){e===void 0&&(e=!1),n===void 0&&(n=!1);let i=t.getBoundingClientRect(),r=vt(t),s=L(1);e&&(o?S(o)&&(s=Q(o)):s=Q(t));let c=fe(r,n,o)?_t(r):L(0),a=(i.left+c.x)/s.x,l=(i.top+c.y)/s.y,d=i.width/s.x,u=i.height/s.y;if(r){let g=O(r),f=o&&S(o)?O(o):o,m=g,h=gt(m);for(;h&&o&&f!==m;){let w=Q(h),p=h.getBoundingClientRect(),x=C(h),y=p.left+(h.clientLeft+parseFloat(x.paddingLeft))*w.x,b=p.top+(h.clientTop+parseFloat(x.paddingTop))*w.y;a*=w.x,l*=w.y,d*=w.x,u*=w.y,a+=y,l+=b,m=O(h),h=gt(m)}}return X({width:d,height:u,x:a,y:l})}function ht(t,e){let n=rt(t).scrollLeft;return e?e.left+n:q(E(t)).left+n}function zt(t,e){let n=t.getBoundingClientRect(),o=n.left+e.scrollLeft-ht(t,n),i=n.top+e.scrollTop;return{x:o,y:i}}function ue(t){let{elements:e,rect:n,offsetParent:o,strategy:i}=t,r=i==="fixed",s=E(o),c=e?st(e.floating):!1;if(o===s||c&&r)return n;let a={scrollLeft:0,scrollTop:0},l=L(1),d=L(0),u=D(o);if((u||!u&&!r)&&((j(o)!=="body"||J(s))&&(a=rt(o)),D(o))){let f=q(o);l=Q(o),d.x=f.x+o.clientLeft,d.y=f.y+o.clientTop}let g=s&&!u&&!r?zt(s,a):L(0);return{width:n.width*l.x,height:n.height*l.y,x:n.x*l.x-a.scrollLeft*l.x+d.x+g.x,y:n.y*l.y-a.scrollTop*l.y+d.y+g.y}}function de(t){return Array.from(t.getClientRects())}function me(t){let e=E(t),n=rt(t),o=t.ownerDocument.body,i=B(e.scrollWidth,e.clientWidth,o.scrollWidth,o.clientWidth),r=B(e.scrollHeight,e.clientHeight,o.scrollHeight,o.clientHeight),s=-n.scrollLeft+ht(t),c=-n.scrollTop;return C(o).direction==="rtl"&&(s+=B(e.clientWidth,o.clientWidth)-i),{width:i,height:r,x:s,y:c}}var Nt=25;function ge(t,e){let n=O(t),o=E(t),i=n.visualViewport,r=o.clientWidth,s=o.clientHeight,c=0,a=0;if(i){r=i.width,s=i.height;let d=mt();(!d||d&&e==="fixed")&&(c=i.offsetLeft,a=i.offsetTop)}let l=ht(o);if(l<=0){let d=o.ownerDocument,u=d.body,g=getComputedStyle(u),f=d.compatMode==="CSS1Compat"&&parseFloat(g.marginLeft)+parseFloat(g.marginRight)||0,m=Math.abs(o.clientWidth-u.clientWidth-f);m<=Nt&&(r-=m)}else l<=Nt&&(r+=l);return{width:r,height:s,x:c,y:a}}var he=new Set(["absolute","fixed"]);function pe(t,e){let n=q(t,!0,e==="fixed"),o=n.top+t.clientTop,i=n.left+t.clientLeft,r=D(t)?Q(t):L(1),s=t.clientWidth*r.x,c=t.clientHeight*r.y,a=i*r.x,l=o*r.y;return{width:s,height:c,x:a,y:l}}function $t(t,e,n){let o;if(e==="viewport")o=ge(t,n);else if(e==="document")o=me(E(t));else if(S(e))o=pe(e,n);else{let i=_t(t);o={x:e.x-i.x,y:e.y-i.y,width:e.width,height:e.height}}return X(o)}function It(t,e){let n=N(t);return n===e||!S(n)||Y(n)?!1:C(n).position==="fixed"||It(n,e)}function we(t,e){let n=e.get(t);if(n)return n;let o=G(t,[],!1).filter(c=>S(c)&&j(c)!=="body"),i=null,r=C(t).position==="fixed",s=r?N(t):t;for(;S(s)&&!Y(s);){let c=C(s),a=dt(s);!a&&c.position==="fixed"&&(i=null),(r?!a&&!i:!a&&c.position==="static"&&!!i&&he.has(i.position)||J(s)&&!a&&It(t,s))?o=o.filter(d=>d!==s):i=c,s=N(s)}return e.set(t,o),o}function xe(t){let{element:e,boundary:n,rootBoundary:o,strategy:i}=t,s=[...n==="clippingAncestors"?st(e)?[]:we(e,this._c):[].concat(n),o],c=s[0],a=s.reduce((l,d)=>{let u=$t(e,d,i);return l.top=B(u.top,l.top),l.right=H(u.right,l.right),l.bottom=H(u.bottom,l.bottom),l.left=B(u.left,l.left),l},$t(e,c,i));return{width:a.right-a.left,height:a.bottom-a.top,x:a.left,y:a.top}}function ye(t){let{width:e,height:n}=Vt(t);return{width:e,height:n}}function ve(t,e,n){let o=D(e),i=E(e),r=n==="fixed",s=q(t,!0,r,e),c={scrollLeft:0,scrollTop:0},a=L(0);function l(){a.x=ht(i)}if(o||!o&&!r)if((j(e)!=="body"||J(i))&&(c=rt(e)),o){let f=q(e,!0,r,e);a.x=f.x+e.clientLeft,a.y=f.y+e.clientTop}else i&&l();r&&!o&&i&&l();let d=i&&!o&&!r?zt(i,c):L(0),u=s.left+c.scrollLeft-a.x-d.x,g=s.top+c.scrollTop-a.y-d.y;return{x:u,y:g,width:s.width,height:s.height}}function yt(t){return C(t).position==="static"}function Ht(t,e){if(!D(t)||C(t).position==="fixed")return null;if(e)return e(t);let n=t.offsetParent;return E(t)===n&&(n=n.ownerDocument.body),n}function Xt(t,e){let n=O(t);if(st(t))return n;if(!D(t)){let i=N(t);for(;i&&!Y(i);){if(S(i)&&!yt(i))return i;i=N(i)}return n}let o=Ht(t,e);for(;o&&kt(o)&&yt(o);)o=Ht(o,e);return o&&Y(o)&&yt(o)&&!dt(o)?n:o||Bt(t)||n}var be=async function(t){let e=this.getOffsetParent||Xt,n=this.getDimensions,o=await n(t.floating);return{reference:ve(t.reference,await e(t.floating),t.strategy),floating:{x:0,y:0,width:o.width,height:o.height}}};function Ae(t){return C(t).direction==="rtl"}var Oe={convertOffsetParentRelativeRectToViewportRelativeRect:ue,getDocumentElement:E,getClippingRect:xe,getOffsetParent:Xt,getElementRects:be,getClientRects:de,getDimensions:ye,getScale:Q,isElement:S,isRTL:Ae};function jt(t,e){return t.x===e.x&&t.y===e.y&&t.width===e.width&&t.height===e.height}function Re(t,e){let n=null,o,i=E(t);function r(){var c;clearTimeout(o),(c=n)==null||c.disconnect(),n=null}function s(c,a){c===void 0&&(c=!1),a===void 0&&(a=1),r();let l=t.getBoundingClientRect(),{left:d,top:u,width:g,height:f}=l;if(c||e(),!g||!f)return;let m=it(u),h=it(i.clientWidth-(d+g)),w=it(i.clientHeight-(u+f)),p=it(d),y={rootMargin:-m+"px "+-h+"px "+-w+"px "+-p+"px",threshold:B(0,H(1,a))||1},b=!0;function v(M){let R=M[0].intersectionRatio;if(R!==a){if(!b)return s();R?s(!1,R):o=setTimeout(()=>{s(!1,1e-7)},1e3)}R===1&&!jt(l,t.getBoundingClientRect())&&s(),b=!1}try{n=new IntersectionObserver(v,{...y,root:i.ownerDocument})}catch{n=new IntersectionObserver(v,y)}n.observe(t)}return s(!0),r}function Se(t,e,n,o){o===void 0&&(o={});let{ancestorScroll:i=!0,ancestorResize:r=!0,elementResize:s=typeof ResizeObserver=="function",layoutShift:c=typeof IntersectionObserver=="function",animationFrame:a=!1}=o,l=vt(t),d=i||r?[...l?G(l):[],...G(e)]:[];d.forEach(p=>{i&&p.addEventListener("scroll",n,{passive:!0}),r&&p.addEventListener("resize",n)});let u=l&&c?Re(l,n):null,g=-1,f=null;s&&(f=new ResizeObserver(p=>{let[x]=p;x&&x.target===l&&f&&(f.unobserve(e),cancelAnimationFrame(g),g=requestAnimationFrame(()=>{var y;(y=f)==null||y.observe(e)})),n()}),l&&!a&&f.observe(l),f.observe(e));let m,h=a?q(t):null;a&&w();function w(){let p=q(t);h&&!jt(h,p)&&n(),h=p,m=requestAnimationFrame(w)}return n(),()=>{var p;d.forEach(x=>{i&&x.removeEventListener("scroll",n),r&&x.removeEventListener("resize",n)}),u?.(),(p=f)==null||p.disconnect(),f=null,a&&cancelAnimationFrame(m)}}var Ce=Et;var Pe=Dt,Te=Lt;var Le=Tt;var Ee=(t,e,n)=>{let o=new Map,i={platform:Oe,...n},r={...i.platform,_c:o};return Pt(t,e,{...i,platform:r})};export{Le as arrow,Se as autoUpdate,Ee as computePosition,Te as flip,Ce as offset,Pe as shift};