diff --git a/src/Toolkit/kits/shadcn/navigation-menu/assets/controllers/navigation_menu_controller.js b/src/Toolkit/kits/shadcn/navigation-menu/assets/controllers/navigation_menu_controller.js index e02b422f169..74ddb38f071 100644 --- a/src/Toolkit/kits/shadcn/navigation-menu/assets/controllers/navigation_menu_controller.js +++ b/src/Toolkit/kits/shadcn/navigation-menu/assets/controllers/navigation_menu_controller.js @@ -158,17 +158,18 @@ export default class extends Controller { } // Anchor to the trigger's inline-start, but shift so the content never overflows the viewport. + // The edge is rounded to a whole pixel: text on a fractional pixel is not always antialiased the same way. #viewportInlineStart(navRect, itemRect, width, rtl) { const margin = 8; if (rtl) { const right = Math.max(width + margin, Math.min(itemRect.right, window.innerWidth - margin)); - return navRect.right - right; + return navRect.right - Math.round(right); } const left = Math.max(margin, Math.min(itemRect.left, window.innerWidth - margin - width)); - return left - navRect.left; + return Math.round(left) - navRect.left; } #setOpen(open, activeItem = null) { diff --git a/src/Toolkit/kits/shadcn/navigation-menu/tests/navigation-menu.spec.ts b/src/Toolkit/kits/shadcn/navigation-menu/tests/navigation-menu.spec.ts index 9b38066cc6a..db03e316ba8 100644 --- a/src/Toolkit/kits/shadcn/navigation-menu/tests/navigation-menu.spec.ts +++ b/src/Toolkit/kits/shadcn/navigation-menu/tests/navigation-menu.spec.ts @@ -88,6 +88,31 @@ describeRecipe('shadcn/navigation-menu', () => { await expect(page.getByRole('link', { name: /Introduction/ })).toBeVisible(); }); + for (const { example, trigger, edge } of [ + { example: 'default', trigger: 'Getting started', edge: 'left' }, + { example: 'rtl', trigger: 'ابدأ', edge: 'right' }, + ] as const) { + test(`aligns the ${edge} edge of the panel on a whole pixel in the ${example} example`, async ({ + page, + gotoExample, + }) => { + await gotoExample(`shadcn/navigation-menu/${example}`); + const viewport = page.locator('[data-slot="navigation-menu-viewport"][data-state="open"]'); + + await page.getByRole('button', { name: trigger }).click(); + await expect(viewport).toBeVisible(); + await viewport + .locator('..') + .evaluate((positioner) => + Promise.all(positioner.getAnimations({ subtree: true }).map((animation) => animation.finished)) + ); + + const box = (await viewport.boundingBox())!; + const position = 'left' === edge ? box.x : box.x + box.width; + expect(position).toBe(Math.round(position)); + }); + } + test('switches to another item at once while one is open', async ({ page, gotoExample }) => { await gotoExample('shadcn/navigation-menu/default', { timers: 'fake' }); const gettingStarted = page.getByRole('button', { name: 'Getting started' }); diff --git a/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-after-move-light.png b/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-after-move-light.png index 60a5b18a1e1..7470fbd9979 100644 Binary files a/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-after-move-light.png and b/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-after-move-light.png differ diff --git a/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-dark.png b/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-dark.png index 50ba0134518..9b81fdf9c7e 100644 Binary files a/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-dark.png and b/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-dark.png differ diff --git a/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-light.png b/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-light.png index 60a5b18a1e1..7470fbd9979 100644 Binary files a/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-light.png and b/src/Toolkit/kits/shadcn/navigation-menu/tests/screenshots/default-open-light.png differ