diff --git a/packages/calcite-components/src/components/dropdown/dropdown.e2e.ts b/packages/calcite-components/src/components/dropdown/dropdown.e2e.ts index 1d7029959a0..2ca69b96f5e 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.e2e.ts +++ b/packages/calcite-components/src/components/dropdown/dropdown.e2e.ts @@ -1308,5 +1308,133 @@ describe("calcite-dropdown", () => { expect(await isElementFocused(page, "#item-3")).toBe(true); }); + + it("should open the dropdown and focus the first item with ArrowDown", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + Open + + 1 + 2 + 3 + + + `); + await skipAnimations(page); + + const dropdown = await page.find("calcite-dropdown"); + await dropdown.callMethod("setFocus"); + await page.waitForChanges(); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await dropdown.getProperty("open")).toBe(true); + expect(await isElementFocused(page, "#item-1")).toBe(true); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-2")).toBe(true); + + await page.keyboard.press("ArrowUp"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-1")).toBe(true); + }); + + it("should open the dropdown and focus the last item with ArrowUp", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + Open + + 1 + 2 + 3 + + + `); + await skipAnimations(page); + + const dropdown = await page.find("calcite-dropdown"); + await dropdown.callMethod("setFocus"); + await page.waitForChanges(); + + await page.keyboard.press("ArrowUp"); + await page.waitForChanges(); + expect(await dropdown.getProperty("open")).toBe(true); + expect(await isElementFocused(page, "#item-3")).toBe(true); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-1")).toBe(true); + + await page.keyboard.press("ArrowUp"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-3")).toBe(true); + }); + + it("should open the dropdown and focus the selected item with ArrowDown", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + Open + + 1 + 2 + 3 + + + `); + await skipAnimations(page); + + const dropdown = await page.find("calcite-dropdown"); + await dropdown.callMethod("setFocus"); + await page.waitForChanges(); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await dropdown.getProperty("open")).toBe(true); + expect(await isElementFocused(page, "#item-2")).toBe(true); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-3")).toBe(true); + + await page.keyboard.press("ArrowUp"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-2")).toBe(true); + }); + + it("should open the dropdown and focus the selected item with ArrowUp", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + Open + + 1 + 2 + 3 + + + `); + await skipAnimations(page); + + const dropdown = await page.find("calcite-dropdown"); + await dropdown.callMethod("setFocus"); + await page.waitForChanges(); + + await page.keyboard.press("ArrowUp"); + await page.waitForChanges(); + expect(await dropdown.getProperty("open")).toBe(true); + expect(await isElementFocused(page, "#item-2")).toBe(true); + + await page.keyboard.press("ArrowUp"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-1")).toBe(true); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await isElementFocused(page, "#item-2")).toBe(true); + }); }); }); diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index bbaa77711b0..8e6d5ced616 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.tsx +++ b/packages/calcite-components/src/components/dropdown/dropdown.tsx @@ -238,7 +238,7 @@ export class Dropdown
@@ -354,7 +354,7 @@ export class Dropdown return; } - this.openCalciteDropdown(); + this.toggleDropdown(); } @Listen("pointerleave") @@ -445,6 +445,8 @@ export class Dropdown guid = `calcite-dropdown-${guid()}`; + private focusLastDropdownItem = false; + //-------------------------------------------------------------------------- // // Private Methods @@ -573,24 +575,25 @@ export class Dropdown return; } - if (this.open) { - if (key === "Escape") { - this.closeCalciteDropdown(); - event.preventDefault(); - return; - } else if (event.shiftKey && key === "Tab") { - this.closeCalciteDropdown(); - event.preventDefault(); - return; - } + if (key === "Escape") { + this.closeCalciteDropdown(); + event.preventDefault(); + return; } - if (isActivationKey(key)) { - this.openCalciteDropdown(); - event.preventDefault(); - } else if (key === "Escape") { + if (this.open && event.shiftKey && key === "Tab") { this.closeCalciteDropdown(); event.preventDefault(); + return; + } + + if (isActivationKey(key)) { + this.toggleDropdown(); + event.preventDefault(); + } else if (key === "ArrowDown" || key === "ArrowUp") { + this.focusLastDropdownItem = key === "ArrowUp"; + this.open = true; + this.el.addEventListener("calciteDropdownOpen", this.onOpenEnd); } }; @@ -634,29 +637,30 @@ export class Dropdown } } - private focusOnFirstActiveOrFirstItem = (): void => { - this.getFocusableElement( - this.getTraversableItems().find((item) => item.selected) || this.items[0], - ); - }; + private focusOnFirstActiveOrDefaultItem = (): void => { + const selectedItem = this.getTraversableItems().find((item) => item.selected); + const target: HTMLCalciteDropdownItemElement = + selectedItem || + (this.focusLastDropdownItem ? this.items[this.items.length - 1] : this.items[0]); + + this.focusLastDropdownItem = false; - private getFocusableElement(item: HTMLCalciteDropdownItemElement): void { - if (!item) { + if (!target) { return; } - focusElement(item); - } + focusElement(target); + }; - private toggleOpenEnd = (): void => { - this.focusOnFirstActiveOrFirstItem(); - this.el.removeEventListener("calciteDropdownOpen", this.toggleOpenEnd); + private onOpenEnd = (): void => { + this.focusOnFirstActiveOrDefaultItem(); + this.el.removeEventListener("calciteDropdownOpen", this.onOpenEnd); }; - private openCalciteDropdown = () => { + private toggleDropdown = () => { this.open = !this.open; if (this.open) { - this.el.addEventListener("calciteDropdownOpen", this.toggleOpenEnd); + this.el.addEventListener("calciteDropdownOpen", this.onOpenEnd); } };