diff --git a/examples/chat/angular/e2e/app-mode-itinerary.spec.ts b/examples/chat/angular/e2e/app-mode-itinerary.spec.ts index c6c5c3660..76db1bc17 100644 --- a/examples/chat/angular/e2e/app-mode-itinerary.spec.ts +++ b/examples/chat/angular/e2e/app-mode-itinerary.spec.ts @@ -23,6 +23,37 @@ test.describe('App mode — itinerary cockpit', () => { await expect(page.locator('.demo-shell__hamburger')).toBeVisible(); }); + test('the closed thread drawer does not intercept clicks on the cockpit content', async ({ page }) => { + // Regression: in App mode the thread sidenav is forced to drawer mode on + // desktop. Its host stays a fixed, drawer-width, full-height, z-index:1001 + // box even when closed (only the inner panel slides off-screen), so without + // pointer-events:none it swallowed clicks on the itinerary overlay + welcome + // suggestions beneath its left strip. + await openDemo(page, '/sidebar?appmode=on'); + await expect(page.locator('.demo-shell__hamburger')).toBeVisible(); // drawer closed + + const probe = await page.evaluate(() => { + const host = document.querySelector('chat-sidenav') as HTMLElement | null; + if (!host) return { ok: false as const }; + const r = host.getBoundingClientRect(); + const x = r.left + r.width / 2; + const y = r.top + Math.min(r.height / 2, 180); + const hit = document.elementFromPoint(x, y) as HTMLElement | null; + return { + ok: true as const, + pointerEvents: getComputedStyle(host).pointerEvents, + // Does a click over the (transparent) closed-drawer box land on the + // sidenav, or pass through to the content beneath it? + hitInsideSidenav: !!hit && !!hit.closest('chat-sidenav'), + }; + }); + + expect(probe.ok).toBe(true); + if (!probe.ok) return; + expect(probe.pointerEvents).toBe('none'); // the fix: closed host is inert + expect(probe.hitInsideSidenav).toBe(false); // clicks reach the content + }); + test('selecting Embed turns App mode off (coercion)', async ({ page }) => { await openDemo(page, '/sidebar?appmode=on'); await expect(page.locator('app-map-canvas')).toBeAttached(); diff --git a/libs/chat/src/lib/styles/chat-sidenav.styles.ts b/libs/chat/src/lib/styles/chat-sidenav.styles.ts index e5376bfde..cc8c63bb1 100644 --- a/libs/chat/src/lib/styles/chat-sidenav.styles.ts +++ b/libs/chat/src/lib/styles/chat-sidenav.styles.ts @@ -39,6 +39,14 @@ export const CHAT_SIDENAV_STYLES = ` bottom: 0; width: var(--tplane-chat-sidenav-width-drawer); z-index: var(--tplane-chat-z-drawer, 1001); + /* The host is a fixed, drawer-width, full-height box at z-index 1001 that + * stays put whether or not the drawer is open — only the inner .chat-sidenav + * PANEL slides off-screen (translateX(-100%)) when closed. Without this, the + * transparent closed host still intercepts clicks over the left strip of the + * content beneath it (e.g. App-mode forces drawer on desktop, so it covered + * the itinerary overlay + welcome suggestions). Make the host inert; the + * on-screen panel re-enables pointer events below. */ + pointer-events: none; } :host([data-mode="drawer"][data-open="true"]) { box-shadow: 8px 0 32px rgba(0, 0, 0, 0.18); @@ -57,6 +65,10 @@ export const CHAT_SIDENAV_STYLES = ` height: 100%; transition: transform 200ms ease; transform: translateX(-100%); + /* Re-enable pointer events on the panel itself (host is inert above). When + * closed the panel is translated off-screen so this is harmless; when open + * it covers the host box and stays fully interactive. */ + pointer-events: auto; } :host([data-mode="drawer"][data-open="true"]) .chat-sidenav { transform: translateX(0);