diff --git a/.changeset/sour-donuts-smile.md b/.changeset/sour-donuts-smile.md new file mode 100644 index 0000000000..f5657dc961 --- /dev/null +++ b/.changeset/sour-donuts-smile.md @@ -0,0 +1,6 @@ +--- +"@zag-js/popper": minor +--- + +Add support for `hideWhenDetached` positioning option. This can be used in the `positioning` options for floating +components (select, popover, dialog, etc.) diff --git a/packages/utilities/popper/src/get-placement.ts b/packages/utilities/popper/src/get-placement.ts index 1ee52011f7..a74bc644b2 100644 --- a/packages/utilities/popper/src/get-placement.ts +++ b/packages/utilities/popper/src/get-placement.ts @@ -1,5 +1,5 @@ import type { AutoUpdateOptions, Middleware } from "@floating-ui/dom" -import { arrow, autoUpdate, computePosition, flip, limitShift, offset, shift, size } from "@floating-ui/dom" +import { arrow, autoUpdate, computePosition, flip, hide, limitShift, offset, shift, size } from "@floating-ui/dom" import { getWindow, raf } from "@zag-js/dom-query" import { compact, isNull, noop, runIfFn } from "@zag-js/utils" import { getAnchorElement } from "./get-anchor" @@ -44,7 +44,7 @@ function getOffsetMiddleware(arrowElement: HTMLElement | null, opts: Positioning const arrowOffset = (arrowElement?.clientHeight || 0) / 2 const gutter = opts.offset?.mainAxis ?? opts.gutter - const mainAxis = typeof gutter === "number" ? gutter + arrowOffset : gutter ?? arrowOffset + const mainAxis = typeof gutter === "number" ? gutter + arrowOffset : (gutter ?? arrowOffset) const { hasAlign } = getPlacementDetails(placement) const shift = !hasAlign ? opts.shift : undefined @@ -95,6 +95,11 @@ function getSizeMiddleware(opts: PositioningOptions) { }) } +function hideWhenDetachedMiddleware(opts: PositioningOptions) { + if (!opts.hideWhenDetached) return + return hide({ strategy: "referenceHidden", boundary: opts.boundary?.() ?? "clippingAncestors" }) +} + function getAutoUpdateOptions(opts?: boolean | AutoUpdateOptions): AutoUpdateOptions { if (!opts) return {} if (opts === true) { @@ -122,6 +127,7 @@ function getPlacementImpl(referenceOrVirtual: MaybeRectElement, floating: MaybeE shiftArrowMiddleware(arrowEl), transformOriginMiddleware, getSizeMiddleware(options), + hideWhenDetachedMiddleware(options), rectMiddleware, ] @@ -150,6 +156,10 @@ function getPlacementImpl(referenceOrVirtual: MaybeRectElement, floating: MaybeE floating.style.setProperty("--x", `${x}px`) floating.style.setProperty("--y", `${y}px`) + if (options.hideWhenDetached && pos.middlewareData.hide?.referenceHidden) { + floating.style.setProperty("visibility", "hidden") + } + const contentEl = floating.firstElementChild if (contentEl) { diff --git a/packages/utilities/popper/src/types.ts b/packages/utilities/popper/src/types.ts index f1f8eb5e76..e0241ef84a 100644 --- a/packages/utilities/popper/src/types.ts +++ b/packages/utilities/popper/src/types.ts @@ -17,6 +17,10 @@ export interface AnchorRect { } export interface PositioningOptions { + /** + * Whether the popover should be hidden when the reference element is detached + */ + hideWhenDetached?: boolean /** * The strategy to use for positioning */