From 2ec1b7a7bab463ab7729808d6981a5f5f3e201ad Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 20 Aug 2024 20:31:31 -0700 Subject: [PATCH 01/27] fix(panel): tweak focusable content area --- .../src/components/panel/panel.e2e.ts | 169 +++++++++++++----- .../src/components/panel/panel.tsx | 12 +- 2 files changed, 129 insertions(+), 52 deletions(-) diff --git a/packages/calcite-components/src/components/panel/panel.e2e.ts b/packages/calcite-components/src/components/panel/panel.e2e.ts index 03896e1bad0..fd639c8dd20 100644 --- a/packages/calcite-components/src/components/panel/panel.e2e.ts +++ b/packages/calcite-components/src/components/panel/panel.e2e.ts @@ -29,6 +29,55 @@ const panelTemplate = (scrollable = false) => `; +const scrollingContentHtml = html` +

+ Enim nascetur erat faucibus ornare varius arcu fames bibendum habitant felis elit ante. Nibh morbi massa curae; leo + semper diam aenean congue taciti eu porta. Varius faucibus ridiculus donec. Montes sit ligula purus porta ante lacus + habitasse libero cubilia purus! In quis congue arcu maecenas felis cursus pellentesque nascetur porta donec non. + Quisque, rutrum ligula pharetra justo habitasse facilisis rutrum neque. Magnis nostra nec nulla dictumst taciti + consectetur. Non porttitor tempor orci dictumst magna porta vitae. +

+

+ Ipsum nostra tempus etiam augue ullamcorper scelerisque sapien potenti erat nisi gravida. Vehicula sem tristique + sed. Nullam, sociis imperdiet ullamcorper? Dapibus fames primis ridiculus vulputate, habitant inceptos! Nunc + torquent lorem urna vehicula volutpat donec nec. Orci massa eu nec donec enim fames, faucibus quam aenean. Laoreet + tellus tempor quisque ornare lobortis praesent erat senectus natoque consectetur donec imperdiet. Quis sem cum + gravida dictumst a pretium purus aptent amet id. Orci habitasse, praesent facilisis condimentum. Nec elit turpis + leo. +

+

+ Tempus per volutpat diam tempor mauris parturient vulputate leo id libero quisque. Mattis aliquam dictum venenatis + fringilla. Taciti venenatis, ultrices sollicitudin consequat. Sapien fusce est iaculis potenti ut auctor potenti. + Nisi malesuada feugiat vulputate vitae porttitor. Nullam nullam nullam accumsan quis magna in. Elementum, nascetur + gravida cras scelerisque inceptos aenean inceptos potenti. Lobortis condimentum accumsan posuere curabitur fermentum + diam, natoque quisque. Eget placerat sed aptent orci urna fusce magnis. Vel lacus magnis nunc. +

+

+ Enim nascetur erat faucibus ornare varius arcu fames bibendum habitant felis elit ante. Nibh morbi massa curae; leo + semper diam aenean congue taciti eu porta. Varius faucibus ridiculus donec. Montes sit ligula purus porta ante lacus + habitasse libero cubilia purus! In quis congue arcu maecenas felis cursus pellentesque nascetur porta donec non. + Quisque, rutrum ligula pharetra justo habitasse facilisis rutrum neque. Magnis nostra nec nulla dictumst taciti + consectetur. Non porttitor tempor orci dictumst magna porta vitae. +

+

+ Ipsum nostra tempus etiam augue ullamcorper scelerisque sapien potenti erat nisi gravida. Vehicula sem tristique + sed. Nullam, sociis imperdiet ullamcorper? Dapibus fames primis ridiculus vulputate, habitant inceptos! Nunc + torquent lorem urna vehicula volutpat donec nec. Orci massa eu nec donec enim fames, faucibus quam aenean. Laoreet + tellus tempor quisque ornare lobortis praesent erat senectus natoque consectetur donec imperdiet. Quis sem cum + gravida dictumst a pretium purus aptent amet id. Orci habitasse, praesent facilisis condimentum. Nec elit turpis + leo. +

+

+ Tempus per volutpat diam tempor mauris parturient vulputate leo id libero quisque. Mattis aliquam dictum venenatis + fringilla. Taciti venenatis, ultrices sollicitudin consequat. Sapien fusce est iaculis potenti ut auctor potenti. + Nisi malesuada feugiat vulputate vitae porttitor. Nullam nullam nullam accumsan quis magna in. Elementum, nascetur + gravida cras scelerisque inceptos aenean inceptos potenti. Lobortis condimentum accumsan posuere curabitur fermentum + diam, natoque quisque. Eget placerat sed aptent orci urna fusce magnis. Vel lacus magnis nunc. +

+`; + +const scrollingStyle = "height: 200px;"; + describe("calcite-panel", () => { describe("renders", () => { renders("calcite-panel", { display: "flex" }); @@ -101,7 +150,21 @@ describe("calcite-panel", () => { }); describe("disabled", () => { - disabled(`scrolling content`); + disabled(`${scrollingContentHtml}`, { + focusTarget: { + tab: "calcite-panel", + click: "body", + }, + }); + + describe("closable", () => { + disabled(`${scrollingContentHtml}`, { + focusTarget: { + tab: "calcite-panel", + click: "body", + }, + }); + }); }); describe("translation support", () => { @@ -284,15 +347,17 @@ describe("calcite-panel", () => { `); }); - describe("should focus on close button", () => { - focusable(`test`, { - shadowFocusTargetSelector: "calcite-action", + describe("is focusable", () => { + describe("closable and with scrolling content", () => { + focusable(`${scrollingContentHtml}`, { + shadowFocusTargetSelector: "calcite-action", + }); }); - }); - describe("should focus on container", () => { - focusable(`test`, { - shadowFocusTargetSelector: "article", + describe("should focus on container", () => { + focusable(`${scrollingContentHtml}`, { + shadowFocusTargetSelector: `.${CSS.contentWrapper}`, + }); }); }); @@ -455,50 +520,56 @@ describe("calcite-panel", () => { expect(await scrollEl.getProperty("scrollTop")).toBe(100); }); - it("should close when Escape key is pressed and closable is true", async () => { - const page = await newE2EPage(); - await page.setContent("test"); - const panel = await page.find("calcite-panel"); - const calcitePanelClose = await panel.spyOnEvent("calcitePanelClose"); - const container = await page.find(`calcite-panel >>> .${CSS.container}`); - expect(await panel.getProperty("closed")).toBe(false); - expect(await container.isVisible()).toBe(true); - await container.press("Escape"); - await page.waitForChanges(); - expect(await panel.getProperty("closed")).toBe(false); - expect(await container.isVisible()).toBe(true); - panel.setProperty("closable", true); - await page.waitForChanges(); - await container.press("Escape"); - await page.waitForChanges(); - expect(await panel.getProperty("closed")).toBe(true); - expect(await container.isVisible()).toBe(false); - expect(calcitePanelClose).toHaveReceivedEventTimes(1); - }); - - it("should not close when Escape key is prevented and closable is true", async () => { - const page = await newE2EPage(); - await page.setContent("test"); - const panel = await page.find("calcite-panel"); - const calcitePanelClose = await panel.spyOnEvent("calcitePanelClose"); - const container = await page.find(`calcite-panel >>> .${CSS.container}`); - - expect(await panel.getProperty("closed")).toBe(false); - expect(await container.isVisible()).toBe(true); + describe("closable", () => { + // TODO: add test for scrollable content + + it("should close when Escape key is pressed and closable is true", async () => { + const page = await newE2EPage(); + await page.setContent(`${scrollingContentHtml}`); + const panel = await page.find("calcite-panel"); + const calcitePanelClose = await panel.spyOnEvent("calcitePanelClose"); + const contentWrapper = await page.find(`calcite-panel >>> .${CSS.contentWrapper}`); + const container = await page.find(`calcite-panel >>> .${CSS.container}`); + expect(await panel.getProperty("closed")).toBe(false); + expect(await container.isVisible()).toBe(true); + await contentWrapper.press("Escape"); + await page.waitForChanges(); + expect(await panel.getProperty("closed")).toBe(false); + expect(await container.isVisible()).toBe(true); + panel.setProperty("closable", true); + await page.waitForChanges(); + + await contentWrapper.press("Escape"); + await page.waitForChanges(); + expect(await panel.getProperty("closed")).toBe(true); + expect(await container.isVisible()).toBe(false); + expect(calcitePanelClose).toHaveReceivedEventTimes(1); + }); - await page.$eval("calcite-panel", (panel: HTMLCalcitePanelElement) => { - panel.addEventListener("keydown", (event) => { - if (event.key === "Escape") { - event.preventDefault(); - } + it("should not close when Escape key is prevented and closable is true", async () => { + const page = await newE2EPage(); + await page.setContent(`${scrollingContentHtml}`); + const panel = await page.find("calcite-panel"); + const calcitePanelClose = await panel.spyOnEvent("calcitePanelClose"); + const container = await page.find(`calcite-panel >>> .${CSS.container}`); + + expect(await panel.getProperty("closed")).toBe(false); + expect(await container.isVisible()).toBe(true); + + await page.$eval("calcite-panel", (panel: HTMLCalcitePanelElement) => { + panel.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + event.preventDefault(); + } + }); }); - }); - await panel.press("Escape"); - await page.waitForChanges(); + await panel.press("Escape"); + await page.waitForChanges(); - expect(await panel.getProperty("closed")).toBe(false); - expect(await container.isVisible()).toBe(true); - expect(calcitePanelClose).toHaveReceivedEventTimes(0); + expect(await panel.getProperty("closed")).toBe(false); + expect(await container.isVisible()).toBe(true); + expect(calcitePanelClose).toHaveReceivedEventTimes(0); + }); }); }); diff --git a/packages/calcite-components/src/components/panel/panel.tsx b/packages/calcite-components/src/components/panel/panel.tsx index 4459f80cf41..cbe7aff0bc9 100644 --- a/packages/calcite-components/src/components/panel/panel.tsx +++ b/packages/calcite-components/src/components/panel/panel.tsx @@ -290,7 +290,14 @@ export class Panel return; } - panelScrollEl.tabIndex = panelScrollEl.scrollHeight > panelScrollEl.offsetHeight ? 0 : -1; + const hasScrollingContent = panelScrollEl.scrollHeight > panelScrollEl.offsetHeight; + + // intentionally using setAttribute to avoid reflecting -1 so default browser behavior will occur + if (hasScrollingContent) { + panelScrollEl.setAttribute("tabindex", "0"); + } else { + panelScrollEl.removeAttribute("tabindex"); + } }; setContainerRef = (node: HTMLElement): void => { @@ -697,7 +704,7 @@ export class Panel }; render(): VNode { - const { disabled, loading, panelKeyDownHandler, isClosed, closable } = this; + const { disabled, loading, panelKeyDownHandler, isClosed } = this; const panelNode = (