diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index d8f44721f8..6b573c27f5 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -24,6 +24,7 @@ import { useEventListener } from '../../hooks/use-event-listener' import { useId } from '../../hooks/use-id' import { useInert } from '../../hooks/use-inert' import { useIsTouchDevice } from '../../hooks/use-is-touch-device' +import { useOnDisappear } from '../../hooks/use-on-disappear' import { useOutsideClick } from '../../hooks/use-outside-click' import { useOwnerDocument } from '../../hooks/use-owner' import { useRootContainers } from '../../hooks/use-root-containers' @@ -338,24 +339,8 @@ function DialogFn( })() useScrollLock(ownerDocument, scrollLockEnabled, resolveRootContainers) - // Trigger close when the FocusTrap gets hidden - useEffect(() => { - if (dialogState !== DialogStates.Open) return - if (!internalDialogRef.current) return - - let observer = new ResizeObserver((entries) => { - for (let entry of entries) { - let rect = entry.target.getBoundingClientRect() - if (rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0) { - close() - } - } - }) - - observer.observe(internalDialogRef.current) - - return () => observer.disconnect() - }, [dialogState, internalDialogRef, close]) + // Ensure we close the dialog as soon as the dialog itself becomes hidden + useOnDisappear(internalDialogRef, close, dialogState === DialogStates.Open) let [describedby, DescriptionProvider] = useDescriptions() diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index d9d5ed6c8e..852a3eca91 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -31,6 +31,7 @@ import { useEvent } from '../../hooks/use-event' import { useId } from '../../hooks/use-id' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' import { useLatestValue } from '../../hooks/use-latest-value' +import { useOnDisappear } from '../../hooks/use-on-disappear' import { useOutsideClick } from '../../hooks/use-outside-click' import { useResolveButtonType } from '../../hooks/use-resolve-button-type' import { useSyncRefs } from '../../hooks/use-sync-refs' @@ -898,6 +899,9 @@ function OptionsFn( return data.listboxState === ListboxStates.Open })() + // Ensure we close the listbox as soon as the button becomes hidden + useOnDisappear(data.buttonRef, actions.closeListbox, visible) + let initialOption = useRef(null) useEffect(() => { diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index 9cf528ec38..0aa6dcd00f 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -27,6 +27,7 @@ import { useElementSize } from '../../hooks/use-element-size' import { useEvent } from '../../hooks/use-event' import { useId } from '../../hooks/use-id' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' +import { useOnDisappear } from '../../hooks/use-on-disappear' import { useOutsideClick } from '../../hooks/use-outside-click' import { useOwnerDocument } from '../../hooks/use-owner' import { useResolveButtonType } from '../../hooks/use-resolve-button-type' @@ -611,6 +612,9 @@ function ItemsFn( return state.menuState === MenuStates.Open })() + // Ensure we close the menu as soon as the button becomes hidden + useOnDisappear(state.buttonRef, () => dispatch({ type: ActionTypes.CloseMenu }), visible) + // We keep track whether the button moved or not, we only check this when the menu state becomes // closed. If the button moved, then we want to cancel pending transitions to prevent that the // attached `MenuItems` is still transitioning while the button moved away. diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index 9ff7687306..e9554e3c9f 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -29,6 +29,7 @@ import { useEventListener } from '../../hooks/use-event-listener' import { useId } from '../../hooks/use-id' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' import { useLatestValue } from '../../hooks/use-latest-value' +import { useOnDisappear } from '../../hooks/use-on-disappear' import { useOutsideClick } from '../../hooks/use-outside-click' import { useOwnerDocument } from '../../hooks/use-owner' import { useResolveButtonType } from '../../hooks/use-resolve-button-type' @@ -854,6 +855,9 @@ function PanelFn( return state.popoverState === PopoverStates.Open })() + // Ensure we close the popover as soon as the button becomes hidden + useOnDisappear(state.button, () => dispatch({ type: ActionTypes.ClosePopover }), visible) + let handleKeyDown = useEvent((event: ReactKeyboardEvent) => { switch (event.key) { case Keys.Escape: