diff --git a/ui/src/components/app-sidebar.ts b/ui/src/components/app-sidebar.ts index 8ba3b283b26..2f26f0aa18c 100644 --- a/ui/src/components/app-sidebar.ts +++ b/ui/src/components/app-sidebar.ts @@ -614,16 +614,21 @@ class AppSidebar extends LitElement { } } - private openSessionSortMenu(x: number, y: number, trigger: HTMLElement | null = null) { + private toggleSessionSortMenu(trigger: HTMLElement) { + if (this.sessionSortMenuPosition) { + this.closeSessionSortMenu(); + return; + } const menuWidth = 200; const menuMaxHeight = 280; + const rect = trigger.getBoundingClientRect(); this.closeCustomizeMenu(); this.closeSessionMenu(); this.closeSessionGroupMenu(); this.sessionSortMenuTrigger = trigger; this.sessionSortMenuPosition = { - x: Math.max(8, Math.min(x, window.innerWidth - menuWidth - 8)), - y: Math.max(8, Math.min(y, window.innerHeight - menuMaxHeight - 8)), + x: Math.max(8, Math.min(rect.right, window.innerWidth - menuWidth - 8)), + y: Math.max(8, Math.min(rect.bottom + 4, window.innerHeight - menuMaxHeight - 8)), }; document.addEventListener("pointerdown", this.handleDocumentPointerDown, true); document.addEventListener("keydown", this.handleDocumentKeydown, true); @@ -764,6 +769,9 @@ class AppSidebar extends LitElement { private readonly handleDocumentPointerDown = (event: PointerEvent) => { const path = event.composedPath(); + if (this.sessionSortMenuTrigger && path.includes(this.sessionSortMenuTrigger)) { + return; + } const menu = this.querySelector( ".sidebar-customize-menu, .sidebar-session-menu, .sidebar-session-sort-menu", ); @@ -1496,8 +1504,7 @@ class AppSidebar extends LitElement { aria-expanded=${String(this.sessionSortMenuPosition !== null)} @click=${(event: MouseEvent) => { const trigger = event.currentTarget as HTMLElement; - const rect = trigger.getBoundingClientRect(); - this.openSessionSortMenu(rect.right, rect.bottom + 4, trigger); + this.toggleSessionSortMenu(trigger); }} > ${icons.listFilter} diff --git a/ui/src/e2e/session-management.e2e.test.ts b/ui/src/e2e/session-management.e2e.test.ts index 48b890da5ec..36a693f8e76 100644 --- a/ui/src/e2e/session-management.e2e.test.ts +++ b/ui/src/e2e/session-management.e2e.test.ts @@ -445,9 +445,16 @@ describeControlUiE2e("Control UI session management mocked Gateway E2E", () => { }); // Group by "None" flattens the category sections into the plain list. - await page.getByRole("button", { name: "Sort sessions" }).click(); + const sortSessionsButton = page.getByRole("button", { name: "Sort sessions" }); + await sortSessionsButton.click(); await page.getByRole("menuitemradio", { name: "None" }).waitFor({ state: "visible" }); await captureUiProof(page, "sidebar-groupby-sort-menu.png"); + await sortSessionsButton.click(); + await expect.poll(() => sortSessionsButton.getAttribute("aria-expanded")).toBe("false"); + await expect.poll(() => page.getByRole("menuitemradio", { name: "None" }).count()).toBe(0); + await captureUiProof(page, "sidebar-groupby-sort-menu-closed.png"); + + await sortSessionsButton.click(); await page.getByRole("menuitemradio", { name: "None" }).click(); await expect.poll(() => groups.count()).toBe(1); await expect.poll(() => groups.first().locator(".sidebar-recent-session").count()).toBe(3);