From 66b157e1a8ddd93d538d4dab28d54192d62ca87b Mon Sep 17 00:00:00 2001 From: Roman Vyakhirev Date: Tue, 19 May 2026 15:52:44 +0200 Subject: [PATCH 1/5] chore(popup-menu): remove modal pprop, it is either hardcoded to true or true by default --- .../pluggableWidgets/popup-menu-web/src/components/Menu.tsx | 4 ++-- .../pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/pluggableWidgets/popup-menu-web/src/components/Menu.tsx b/packages/pluggableWidgets/popup-menu-web/src/components/Menu.tsx index d536d9e819..05b3c66fde 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/components/Menu.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/components/Menu.tsx @@ -10,7 +10,7 @@ export interface MenuProps extends PopupMenuContainerProps { } export const Menu = forwardRef((props: MenuProps, propRef: RefObject): ReactElement | null => { - const { context: floatingContext, floatingStyles, getFloatingProps, modal, refs } = usePopupContext(); + const { context: floatingContext, floatingStyles, getFloatingProps, refs } = usePopupContext(); const ref = useMergeRefs([refs.setFloating, propRef]); if (!floatingContext.open) { @@ -20,7 +20,7 @@ export const Menu = forwardRef((props: MenuProps, propRef: RefObject +
    void; clippingStrategy?: ClippingStrategyEnum; @@ -30,13 +29,11 @@ type InteractionReturn = Pick Date: Tue, 19 May 2026 15:59:38 +0200 Subject: [PATCH 2/5] chore(popup-menu-web): make props that are always passed non-optional --- .../src/__tests__/PopupTriggerWithContext.tsx | 8 +++++++- .../popup-menu-web/src/__tests__/usePopup.spec.tsx | 8 +++++++- .../popup-menu-web/src/hooks/usePopup.ts | 12 ++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx b/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx index 1a63c0a33f..9066f3631c 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx @@ -4,7 +4,13 @@ import { PopupTrigger } from "../components/PopupTrigger"; import { usePopup } from "../hooks/usePopup"; export function PopupTriggerWithContext(props: PropsWithChildren): ReactElement { - const popup = usePopup({ open: true, onOpenChange: jest.fn(), trigger: "onclick", clippingStrategy: "absolute" }); + const popup = usePopup({ + open: true, + onOpenChange: jest.fn(), + trigger: "onclick", + clippingStrategy: "absolute", + placement: "top" + }); return ( diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx b/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx index 8480e60e9d..90cbfaed67 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx @@ -5,7 +5,13 @@ import { usePopup } from "../hooks/usePopup"; describe("usePopup", () => { it("somethign", () => { const popup = renderHook(() => { - return usePopup({ open: false, onOpenChange: jest.fn(), trigger: "onclick", clippingStrategy: "absolute" }); + return usePopup({ + open: false, + onOpenChange: jest.fn(), + trigger: "onclick", + clippingStrategy: "absolute", + placement: "top" + }); }); expect(popup.result.current.open).toBe(false); diff --git a/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts b/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts index 49d35c5d70..bc2dddb6d4 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts +++ b/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts @@ -17,11 +17,11 @@ import { import { ClippingStrategyEnum, TriggerEnum } from "../../typings/PopupMenuProps"; interface PopupOptions { - placement?: Placement; - open?: boolean; + placement: Placement; + open: boolean; onOpenChange?: (open: boolean) => void; - clippingStrategy?: ClippingStrategyEnum; - trigger?: TriggerEnum; + clippingStrategy: ClippingStrategyEnum; + trigger: TriggerEnum; } type FloatingReturn = Pick; @@ -29,7 +29,7 @@ type InteractionReturn = Pick Date: Tue, 19 May 2026 16:36:35 +0200 Subject: [PATCH 3/5] fix(popup-menu): do not close popup on leave when close mode is onClickOutside --- .../src/__tests__/MenuWithContext.tsx | 3 ++- .../src/__tests__/PopupTriggerWithContext.tsx | 3 ++- .../src/__tests__/usePopup.spec.tsx | 3 ++- .../src/components/PopupMenu.tsx | 3 ++- .../popup-menu-web/src/hooks/usePopup.ts | 19 ++++++++++++++++--- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/MenuWithContext.tsx b/packages/pluggableWidgets/popup-menu-web/src/__tests__/MenuWithContext.tsx index 2e56ff6f5d..cf4aa305d5 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/MenuWithContext.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/MenuWithContext.tsx @@ -9,7 +9,8 @@ export function MenuWithContext(props: MenuProps): ReactElement { onOpenChange: jest.fn(), placement: props.position, trigger: props.trigger, - clippingStrategy: props.clippingStrategy + clippingStrategy: props.clippingStrategy, + hoverCloseOn: props.hoverCloseOn }); return ( diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx b/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx index 9066f3631c..10ec4065a5 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/PopupTriggerWithContext.tsx @@ -9,7 +9,8 @@ export function PopupTriggerWithContext(props: PropsWithChildren): ReactElement onOpenChange: jest.fn(), trigger: "onclick", clippingStrategy: "absolute", - placement: "top" + placement: "top", + hoverCloseOn: "onClickOutside" }); return ( diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx b/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx index 90cbfaed67..54eb9a33d5 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/usePopup.spec.tsx @@ -10,7 +10,8 @@ describe("usePopup", () => { onOpenChange: jest.fn(), trigger: "onclick", clippingStrategy: "absolute", - placement: "top" + placement: "top", + hoverCloseOn: "onClickOutside" }); }); diff --git a/packages/pluggableWidgets/popup-menu-web/src/components/PopupMenu.tsx b/packages/pluggableWidgets/popup-menu-web/src/components/PopupMenu.tsx index bd524ea1a9..1a18d6b522 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/components/PopupMenu.tsx +++ b/packages/pluggableWidgets/popup-menu-web/src/components/PopupMenu.tsx @@ -16,7 +16,8 @@ export function PopupMenu(props: PopupMenuContainerProps): ReactElement { onOpenChange: setVisibility, placement: props.position, trigger: props.trigger, - clippingStrategy: props.clippingStrategy + clippingStrategy: props.clippingStrategy, + hoverCloseOn: props.hoverCloseOn }); const handleOnClickItem = useCallback( diff --git a/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts b/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts index bc2dddb6d4..0be9822ac9 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts +++ b/packages/pluggableWidgets/popup-menu-web/src/hooks/usePopup.ts @@ -14,7 +14,7 @@ import { UseInteractionsReturn, useRole } from "@floating-ui/react"; -import { ClippingStrategyEnum, TriggerEnum } from "../../typings/PopupMenuProps"; +import { ClippingStrategyEnum, HoverCloseOnEnum, TriggerEnum } from "../../typings/PopupMenuProps"; interface PopupOptions { placement: Placement; @@ -22,6 +22,7 @@ interface PopupOptions { onOpenChange?: (open: boolean) => void; clippingStrategy: ClippingStrategyEnum; trigger: TriggerEnum; + hoverCloseOn: HoverCloseOnEnum; } type FloatingReturn = Pick; @@ -37,7 +38,8 @@ export function usePopup({ open, onOpenChange: setOpen, trigger, - clippingStrategy + clippingStrategy, + hoverCloseOn }: PopupOptions): UsePopupReturn { const { context, floatingStyles, refs } = useFloating({ middleware: [offset(5), flip(), shift()], @@ -51,7 +53,11 @@ export function usePopup({ const dismiss = useDismiss(context); const role = useRole(context); const click = useClick(context, { enabled: trigger === "onclick" }); - const hover = useHover(context, { enabled: trigger === "onhover", handleClose: safePolygon() }); + + const hover = useHover(context, { + enabled: trigger === "onhover", + handleClose: hoverCloseOn === "onHoverLeave" ? safePolygon() : neverClose + }); const { getFloatingProps, getReferenceProps } = useInteractions([dismiss, role, click, hover]); @@ -64,3 +70,10 @@ export function usePopup({ refs }; } + +const neverClose = Object.assign( + (): (() => void) => { + return (): void => {}; + }, + { __options: { blockPointerEvents: false } } +); From 4b6e1229a41d2a86dd706ec560906a660f59db3f Mon Sep 17 00:00:00 2001 From: Roman Vyakhirev Date: Tue, 19 May 2026 17:19:04 +0200 Subject: [PATCH 4/5] fix(popup-menu): fix issue with nested dialogs hover --- .../popup-menu-web/CHANGELOG.md | 5 ++++ .../__snapshots__/Menu.spec.tsx.snap | 2 +- .../__snapshots__/PopupMenu.spec.tsx.snap | 4 +-- .../__snapshots__/PopupTrigger.spec.tsx.snap | 2 +- .../src/components/PopupMenu.tsx | 29 +++++++++++++++---- .../popup-menu-web/src/hooks/usePopup.ts | 8 ++++- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/pluggableWidgets/popup-menu-web/CHANGELOG.md b/packages/pluggableWidgets/popup-menu-web/CHANGELOG.md index fdc8e7cc33..897959458e 100644 --- a/packages/pluggableWidgets/popup-menu-web/CHANGELOG.md +++ b/packages/pluggableWidgets/popup-menu-web/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We fixed an issue where the "Close on" option for the Hover trigger was not working. The menu will now correctly stay open until you click outside when "Click outside" is selected, or close when you hover away when "Hover leave" is selected. +- We fixed an issue where nested popup menus (a popup menu inside another popup menu's content) would close unexpectedly when hovering between parent and child menus. + ## [4.2.0] - 2026-05-04 ### Added diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/Menu.spec.tsx.snap b/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/Menu.spec.tsx.snap index cecd9fde46..b64ac23785 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/Menu.spec.tsx.snap +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/Menu.spec.tsx.snap @@ -18,7 +18,7 @@ exports[`Menu renders menu 1`] = ` class="popupmenu-menu" data-floating-ui-focusable="" data-overlay-content="true" - id=":r0:" + id=":r1:" role="dialog" style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);" tabindex="0" diff --git a/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/PopupMenu.spec.tsx.snap b/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/PopupMenu.spec.tsx.snap index a274e35331..0507a423a1 100644 --- a/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/PopupMenu.spec.tsx.snap +++ b/packages/pluggableWidgets/popup-menu-web/src/__tests__/__snapshots__/PopupMenu.spec.tsx.snap @@ -6,7 +6,7 @@ exports[`Popup Menu renders popup menu 1`] = ` class="popupmenu mx-popup-menu" >