diff --git a/.changeset/healthy-zebras-cough.md b/.changeset/healthy-zebras-cough.md new file mode 100644 index 0000000000..2d39033460 --- /dev/null +++ b/.changeset/healthy-zebras-cough.md @@ -0,0 +1,5 @@ +--- +"@zag-js/popover": patch +--- + +Fix issue where popover does not restore focus when open state is changed programmatically diff --git a/examples/next-ts/pages/popover-controlled.tsx b/examples/next-ts/pages/popover-controlled.tsx new file mode 100644 index 0000000000..4754155f0a --- /dev/null +++ b/examples/next-ts/pages/popover-controlled.tsx @@ -0,0 +1,40 @@ +import { Fragment, useId, useState } from "react" +import * as popover from "@zag-js/popover" +import { useMachine, normalizeProps, Portal } from "@zag-js/react" + +export default function Page() { + const [open, setOpen] = useState(false) + + const initialContext = { + id: useId(), + open: false, + "open.controlled": true, + } + + const [state, send] = useMachine(popover.machine(initialContext), { + context: { + ...initialContext, + onOpenChange: (details) => setOpen(details.open), + open: open, + }, + }) + + const api = popover.connect(state, send, normalizeProps) + + const Wrapper = api.portalled ? Portal : Fragment + + return ( +
+ + +
+
+
Presenters
+
Description
+ +
+
+
+
+ ) +} diff --git a/packages/machines/popover/src/popover.connect.ts b/packages/machines/popover/src/popover.connect.ts index c4087e1cf8..84a95e2e5d 100644 --- a/packages/machines/popover/src/popover.connect.ts +++ b/packages/machines/popover/src/popover.connect.ts @@ -132,7 +132,7 @@ export function connect(state: State, send: Send, normalize "aria-label": "close", onClick(event) { if (event.defaultPrevented) return - send({ type: "CLOSE", restoreFocus: true }) + send("CLOSE") }, }) }, diff --git a/packages/machines/popover/src/popover.machine.ts b/packages/machines/popover/src/popover.machine.ts index ab0ebd55d8..94ff1014c1 100644 --- a/packages/machines/popover/src/popover.machine.ts +++ b/packages/machines/popover/src/popover.machine.ts @@ -242,7 +242,7 @@ export function machine(userContext: UserDefinedContext) { }, setFinalFocus(ctx, evt) { const restoreFocus = evt.restoreFocus ?? evt.previousEvent?.restoreFocus - if (!restoreFocus) return + if (restoreFocus != null && !restoreFocus) return raf(() => { const element = dom.getTriggerEl(ctx) element?.focus({ preventScroll: true })