From bc8de6f7fa188b8122da06607c858102bc802924 Mon Sep 17 00:00:00 2001 From: Emil Nordling Date: Thu, 20 Nov 2025 11:12:11 +0100 Subject: [PATCH 1/5] adds a test for validating width --- .../trigger/NavigationMenuTrigger.test.tsx | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx index 3d935ca08fa..3493f7f7f80 100644 --- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx +++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx @@ -110,4 +110,46 @@ describe('', () => { parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-height'), 10), ).to.be.approximately(18, 1); }); + + it.skipIf(isJSDOM)('handles positioner width correctly', async () => { + await render( + + + + Overview + + + Handbook + + Styling Base UI components + + + + + + + + + + + , + ); + + const overviewButton = screen.getByRole('button', { name: 'Overview' }); + const handbookButton = screen.getByRole('button', { name: 'Handbook' }); + + await userEvent.pointer([{ target: overviewButton }, { target: handbookButton }]); + + await waitFor(() => { + const handbookLink = screen.getByRole('link', { name: 'Styling Base UI components' }); + + expect(handbookLink).toBeVisible(); + }); + + const positioner = await screen.findByTestId('positioner'); + + expect( + parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-width'), 10), + ).to.be.approximately(183, 1); + }); }); From 3817d5c2d11a573fb7e0628d57c2b952c9d90d77 Mon Sep 17 00:00:00 2001 From: Emil Nordling Date: Sun, 23 Nov 2025 18:15:14 +0100 Subject: [PATCH 2/5] apply patch --- .../trigger/NavigationMenuTrigger.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx index bb311659e3a..dadfe8f87a1 100644 --- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx +++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx @@ -128,25 +128,25 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerWidth); positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerHeight); - const { width, height } = getCssDimensions(popupElement); + sizeFrame1.request(() => { + const { width, height } = getCssDimensions(popupElement); - if (currentHeight === 0 || currentWidth === 0) { - currentWidth = width; - currentHeight = height; - } + if (currentHeight === 0 || currentWidth === 0) { + currentWidth = width; + currentHeight = height; + } - popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${currentWidth}px`); - popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${currentHeight}px`); - positionerElement.style.setProperty( - NavigationMenuPositionerCssVars.positionerWidth, - `${width}px`, - ); - positionerElement.style.setProperty( - NavigationMenuPositionerCssVars.positionerHeight, - `${height}px`, - ); + popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${currentWidth}px`); + popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${currentHeight}px`); + positionerElement.style.setProperty( + NavigationMenuPositionerCssVars.positionerWidth, + `${width}px`, + ); + positionerElement.style.setProperty( + NavigationMenuPositionerCssVars.positionerHeight, + `${height}px`, + ); - sizeFrame1.request(() => { popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${width}px`); popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${height}px`); From 00be9227073f38e583876d2d2ad8948675e6a117 Mon Sep 17 00:00:00 2001 From: Emil Nordling Date: Mon, 24 Nov 2025 09:29:17 +0100 Subject: [PATCH 3/5] reverses timing change. Applies an half hazardly fix --- .../trigger/NavigationMenuTrigger.tsx | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx index dadfe8f87a1..b9e727baef8 100644 --- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx +++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx @@ -128,27 +128,29 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerWidth); positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerHeight); - sizeFrame1.request(() => { - const { width, height } = getCssDimensions(popupElement); + const { width, height } = getCssDimensions(popupElement); + const measuredWidth = width || prevSizeRef.current.width; + const measuredHeight = height || prevSizeRef.current.height; - if (currentHeight === 0 || currentWidth === 0) { - currentWidth = width; - currentHeight = height; - } + if (currentHeight === 0 || currentWidth === 0) { + currentWidth = measuredWidth; + currentHeight = measuredHeight; + } - popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${currentWidth}px`); - popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${currentHeight}px`); - positionerElement.style.setProperty( - NavigationMenuPositionerCssVars.positionerWidth, - `${width}px`, - ); - positionerElement.style.setProperty( - NavigationMenuPositionerCssVars.positionerHeight, - `${height}px`, - ); + popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${currentWidth}px`); + popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${currentHeight}px`); + positionerElement.style.setProperty( + NavigationMenuPositionerCssVars.positionerWidth, + `${measuredWidth}px`, + ); + positionerElement.style.setProperty( + NavigationMenuPositionerCssVars.positionerHeight, + `${measuredHeight}px`, + ); - popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${width}px`); - popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${height}px`); + sizeFrame1.request(() => { + popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${measuredWidth}px`); + popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${measuredHeight}px`); sizeFrame2.request(() => { animationAbortControllerRef.current = new AbortController(); @@ -162,7 +164,6 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri stickIfOpenTimeout.clear(); sizeFrame1.cancel(); sizeFrame2.cancel(); - prevSizeRef.current = DEFAULT_SIZE; } }, [stickIfOpenTimeout, open, sizeFrame1, sizeFrame2]); From 0ad49406ebaea8e8c6c347a48d783b7e5a630fa7 Mon Sep 17 00:00:00 2001 From: Emil Nordling Date: Mon, 24 Nov 2025 09:40:32 +0100 Subject: [PATCH 4/5] fixes the test scenario --- .../navigation-menu/trigger/NavigationMenuTrigger.test.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx index 3493f7f7f80..88e2acd0563 100644 --- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx +++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx @@ -138,7 +138,11 @@ describe('', () => { const overviewButton = screen.getByRole('button', { name: 'Overview' }); const handbookButton = screen.getByRole('button', { name: 'Handbook' }); - await userEvent.pointer([{ target: overviewButton }, { target: handbookButton }]); + await userEvent.pointer([ + { target: overviewButton }, + { target: handbookButton, releasePrevious: true }, + { target: overviewButton, releasePrevious: true }, + ]); await waitFor(() => { const handbookLink = screen.getByRole('link', { name: 'Styling Base UI components' }); From ad68b69b68d0a57566ebcf6bef4c617979297245 Mon Sep 17 00:00:00 2001 From: Emil Nordling Date: Mon, 24 Nov 2025 15:23:06 +0100 Subject: [PATCH 5/5] fix Safari 18.1.1 navigation menu --- .../src/navigation-menu/trigger/NavigationMenuTrigger.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx index b9e727baef8..f65d6c35ed7 100644 --- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx +++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx @@ -160,12 +160,13 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri }); React.useEffect(() => { - if (!open) { + if (!mounted) { stickIfOpenTimeout.clear(); sizeFrame1.cancel(); sizeFrame2.cancel(); + prevSizeRef.current = DEFAULT_SIZE; } - }, [stickIfOpenTimeout, open, sizeFrame1, sizeFrame2]); + }, [stickIfOpenTimeout, mounted, sizeFrame1, sizeFrame2]); React.useEffect(() => { if (!popupElement || typeof ResizeObserver !== 'function') {