diff --git a/packages/machines/popover/src/popover.connect.ts b/packages/machines/popover/src/popover.connect.ts index 164dd69671..5ba12f091e 100644 --- a/packages/machines/popover/src/popover.connect.ts +++ b/packages/machines/popover/src/popover.connect.ts @@ -35,6 +35,9 @@ export function connect(service: PopoverService, normalize: reposition(options = {}) { send({ type: "POSITIONING.SET", options }) }, + restartPositioning() { + send({ type: "POSITIONING.RESTART" }) + }, getArrowProps() { return normalize.element({ diff --git a/packages/machines/popover/src/popover.machine.ts b/packages/machines/popover/src/popover.machine.ts index dbe5913b60..1d97df9bfe 100644 --- a/packages/machines/popover/src/popover.machine.ts +++ b/packages/machines/popover/src/popover.machine.ts @@ -1,5 +1,5 @@ import { ariaHidden } from "@zag-js/aria-hidden" -import { createMachine } from "@zag-js/core" +import { createMachine, type Params } from "@zag-js/core" import { trackDismissableElement } from "@zag-js/dismissable" import { getInitialFocus, proxyTabFocus, raf } from "@zag-js/dom-query" import { trapFocus } from "@zag-js/focus-trap" @@ -8,6 +8,21 @@ import { preventBodyScroll } from "@zag-js/remove-scroll" import * as dom from "./popover.dom" import type { Placement, PopoverSchema } from "./popover.types" +function startPositioning({ context, prop, scope }: Params) { + context.set("currentPlacement", prop("positioning").placement) + const anchorEl = dom.getAnchorEl(scope) + const getPositionerEl = () => dom.getPositionerEl(scope) + const getTriggerEl = () => anchorEl ?? dom.getActiveTriggerEl(scope, context.get("triggerValue")) + + return getPlacement(getTriggerEl, getPositionerEl, { + ...prop("positioning"), + defer: true, + onComplete(data) { + context.set("currentPlacement", data.placement) + }, + }) +} + export const machine = createMachine({ props({ props }) { return { @@ -55,6 +70,12 @@ export const machine = createMachine({ } }, + refs() { + return { + positioningCleanup: null, + } + }, + computed: { currentPortalled: ({ prop }) => !!prop("modal") || !!prop("portalled"), }, @@ -137,6 +158,9 @@ export const machine = createMachine({ actions: ["invokeOnClose"], }, ], + "POSITIONING.RESTART": { + actions: ["restartPositioning"], + }, "POSITIONING.SET": { actions: ["reposition"], }, @@ -149,18 +173,16 @@ export const machine = createMachine({ isOpenControlled: ({ prop }) => prop("open") != undefined, }, effects: { - trackPositioning({ context, prop, scope }) { - context.set("currentPlacement", prop("positioning").placement) - const anchorEl = dom.getAnchorEl(scope) - const getPositionerEl = () => dom.getPositionerEl(scope) - const getTriggerEl = () => anchorEl ?? dom.getActiveTriggerEl(scope, context.get("triggerValue")) - return getPlacement(getTriggerEl, getPositionerEl, { - ...prop("positioning"), - defer: true, - onComplete(data) { - context.set("currentPlacement", data.placement) - }, - }) + trackPositioning(params) { + const { refs } = params + + refs.get("positioningCleanup")?.() + refs.set("positioningCleanup", startPositioning(params)) + + return () => { + refs.get("positioningCleanup")?.() + refs.set("positioningCleanup", null) + } }, trackDismissableElement({ send, prop, scope }) { @@ -251,7 +273,14 @@ export const machine = createMachine({ }, actions: { - reposition({ event, prop, scope, context }) { + restartPositioning(params) { + const { refs } = params + refs.get("positioningCleanup")?.() + refs.set("positioningCleanup", startPositioning(params)) + }, + + reposition(params) { + const { event, prop, scope, context } = params const anchorEl = dom.getAnchorEl(scope) const getPositionerEl = () => dom.getPositionerEl(scope) const getTriggerEl = () => anchorEl ?? dom.getActiveTriggerEl(scope, context.get("triggerValue")) diff --git a/packages/machines/popover/src/popover.types.ts b/packages/machines/popover/src/popover.types.ts index 2c7c798ca6..935b8ff25a 100644 --- a/packages/machines/popover/src/popover.types.ts +++ b/packages/machines/popover/src/popover.types.ts @@ -170,6 +170,9 @@ export interface PopoverSchema { props: RequiredBy state: "open" | "closed" context: PrivateContext + refs: { + positioningCleanup: VoidFunction | null + } computed: ComputedContext event: EventObject action: string @@ -221,6 +224,10 @@ export interface PopoverApi { * Function to reposition the popover */ reposition: (options?: Partial) => void + /** + * Function to restart the tracked positioning session. + */ + restartPositioning: () => void getArrowProps: () => T["element"] getArrowTipProps: () => T["element"]