From d3370e12c3f09593f5dccb74229a344b02bdd277 Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Tue, 10 Sep 2024 16:02:14 -0500 Subject: [PATCH 1/9] fix(dropdown): open dropdown on ArrowDown & ArrowUp keys --- .../src/components/dropdown/dropdown.e2e.ts | 128 ++++++++++++++++++ .../src/components/dropdown/dropdown.tsx | 28 ++-- 2 files changed, 145 insertions(+), 11 deletions(-) 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 30063d80107..00fbc6313f4 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
this.openCalciteDropdown()} onKeyDown={this.keyDownHandler} ref={this.setReferenceEl} > @@ -591,6 +591,9 @@ export class Dropdown } else if (key === "Escape") { this.closeCalciteDropdown(); event.preventDefault(); + } else if (key === "ArrowDown" || key === "ArrowUp") { + this.openCalciteDropdown(key); + return; } }; @@ -634,29 +637,32 @@ export class Dropdown } } - private focusOnFirstActiveOrFirstItem = (): void => { - this.getFocusableElement( - this.getTraversableItems().find((item) => item.selected) || this.items[0], - ); + private focusOnFirstActiveOrDefaultItem = (focusLastItem: boolean): void => { + const selectedItem = this.getTraversableItems().find((item) => item.selected); + if (selectedItem) { + this.getFocusableElement(selectedItem); + return; + } else { + this.getFocusableElement(focusLastItem ? this.items[this.items.length - 1] : this.items[0]); + } }; private getFocusableElement(item: HTMLCalciteDropdownItemElement): void { if (!item) { return; } - focusElement(item); } - private toggleOpenEnd = (): void => { - this.focusOnFirstActiveOrFirstItem(); - this.el.removeEventListener("calciteDropdownOpen", this.toggleOpenEnd); + private toggleOpenEnd = (focusLastItem: boolean): void => { + this.focusOnFirstActiveOrDefaultItem(focusLastItem); + this.el.removeEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(focusLastItem)); }; - private openCalciteDropdown = () => { + private openCalciteDropdown = (key?: string): void => { this.open = !this.open; if (this.open) { - this.el.addEventListener("calciteDropdownOpen", this.toggleOpenEnd); + this.el.addEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(key === "ArrowUp")); } }; From 8940e89f257f19c676ef876b87b1e171b51bcbaa Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Tue, 10 Sep 2024 16:36:07 -0500 Subject: [PATCH 2/9] refactor --- .../src/components/dropdown/dropdown.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index 00fbc6313f4..6cb3c6ac95d 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.tsx +++ b/packages/calcite-components/src/components/dropdown/dropdown.tsx @@ -592,7 +592,7 @@ export class Dropdown this.closeCalciteDropdown(); event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { - this.openCalciteDropdown(key); + this.openCalciteDropdown(key === "ArrowUp"); return; } }; @@ -640,14 +640,16 @@ export class Dropdown private focusOnFirstActiveOrDefaultItem = (focusLastItem: boolean): void => { const selectedItem = this.getTraversableItems().find((item) => item.selected); if (selectedItem) { - this.getFocusableElement(selectedItem); + this.focusDropdownItemElement(selectedItem); return; } else { - this.getFocusableElement(focusLastItem ? this.items[this.items.length - 1] : this.items[0]); + this.focusDropdownItemElement( + focusLastItem ? this.items[this.items.length - 1] : this.items[0], + ); } }; - private getFocusableElement(item: HTMLCalciteDropdownItemElement): void { + private focusDropdownItemElement(item: HTMLCalciteDropdownItemElement): void { if (!item) { return; } @@ -659,10 +661,10 @@ export class Dropdown this.el.removeEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(focusLastItem)); }; - private openCalciteDropdown = (key?: string): void => { + private openCalciteDropdown = (focusLastItem = false): void => { this.open = !this.open; if (this.open) { - this.el.addEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(key === "ArrowUp")); + this.el.addEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(focusLastItem)); } }; From 95a46f14e2afc6b7d4204d25e538ca50ca1078fb Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Tue, 10 Sep 2024 18:13:04 -0500 Subject: [PATCH 3/9] cleanup --- packages/calcite-components/src/components/dropdown/dropdown.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index 6cb3c6ac95d..7abd3f6659c 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.tsx +++ b/packages/calcite-components/src/components/dropdown/dropdown.tsx @@ -593,7 +593,6 @@ export class Dropdown event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { this.openCalciteDropdown(key === "ArrowUp"); - return; } }; From 520fa62a1f8ff7a1dcc53b6c4bb38a03415e3cc7 Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Wed, 11 Sep 2024 12:50:51 -0500 Subject: [PATCH 4/9] feedback changes & refactor --- .../src/components/dropdown/dropdown.tsx | 65 ++++++++----------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index 7abd3f6659c..f747f7884cb 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
this.openCalciteDropdown()} + onClick={this.clickHandler} onKeyDown={this.keyDownHandler} ref={this.setReferenceEl} > @@ -354,7 +354,7 @@ export class Dropdown return; } - this.openCalciteDropdown(); + this.clickHandler(); } @Listen("pointerleave") @@ -445,6 +445,8 @@ export class Dropdown guid = `calcite-dropdown-${guid()}`; + private focusLastDropdownItem = false; + //-------------------------------------------------------------------------- // // Private Methods @@ -541,6 +543,7 @@ export class Dropdown onOpen(): void { this.calciteDropdownOpen.emit(); + this.focusOnFirstActiveOrDefaultItem(); } onBeforeClose(): void { @@ -573,26 +576,24 @@ 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.clickHandler(); + event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { - this.openCalciteDropdown(key === "ArrowUp"); + this.focusLastDropdownItem = key === "ArrowUp"; + this.clickHandler(); } }; @@ -636,35 +637,23 @@ export class Dropdown } } - private focusOnFirstActiveOrDefaultItem = (focusLastItem: boolean): void => { + private focusOnFirstActiveOrDefaultItem = (): void => { const selectedItem = this.getTraversableItems().find((item) => item.selected); - if (selectedItem) { - this.focusDropdownItemElement(selectedItem); - return; - } else { - this.focusDropdownItemElement( - focusLastItem ? this.items[this.items.length - 1] : this.items[0], - ); - } - }; + const target: HTMLCalciteDropdownItemElement = + selectedItem || + (this.focusLastDropdownItem ? this.items[this.items.length - 1] : this.items[0]); + + this.focusLastDropdownItem = false; - private focusDropdownItemElement(item: HTMLCalciteDropdownItemElement): void { - if (!item) { + if (!target) { return; } - focusElement(item); - } - private toggleOpenEnd = (focusLastItem: boolean): void => { - this.focusOnFirstActiveOrDefaultItem(focusLastItem); - this.el.removeEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(focusLastItem)); + focusElement(target); }; - private openCalciteDropdown = (focusLastItem = false): void => { + private clickHandler = (): void => { this.open = !this.open; - if (this.open) { - this.el.addEventListener("calciteDropdownOpen", () => this.toggleOpenEnd(focusLastItem)); - } }; private updateTabIndexOfItems(target: HTMLCalciteDropdownItemElement): void { From 6150a6dfc042f52345d66337c9b06ab45094b935 Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Wed, 11 Sep 2024 13:40:31 -0500 Subject: [PATCH 5/9] arrowkeys wont close dropdown --- .../calcite-components/src/components/dropdown/dropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index f747f7884cb..a7628e0bb73 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.tsx +++ b/packages/calcite-components/src/components/dropdown/dropdown.tsx @@ -593,7 +593,7 @@ export class Dropdown event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { this.focusLastDropdownItem = key === "ArrowUp"; - this.clickHandler(); + this.open = true; } }; From a5c193c9e8aaadcb53996267f1dc52c1ea358ab8 Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Tue, 24 Sep 2024 18:12:53 -0500 Subject: [PATCH 6/9] revert & add eventListeners --- .../src/components/dropdown/dropdown.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index 1fa7ed62ded..dce92da1b95 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.clickHandler(); + this.openDropdown(); } @Listen("pointerleave") @@ -543,7 +543,6 @@ export class Dropdown onOpen(): void { this.calciteDropdownOpen.emit(); - this.focusOnFirstActiveOrDefaultItem(); } onBeforeClose(): void { @@ -589,11 +588,12 @@ export class Dropdown } if (isActivationKey(key)) { - this.clickHandler(); + this.openDropdown(); event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { this.focusLastDropdownItem = key === "ArrowUp"; - this.open = true; + this.openDropdown(); + event.preventDefault(); } }; @@ -652,8 +652,16 @@ export class Dropdown focusElement(target); }; - private clickHandler = (): void => { + private toggleOpenEnd = (): void => { + this.focusOnFirstActiveOrDefaultItem(); + this.el.removeEventListener("calciteDropdownOpen", this.toggleOpenEnd); + }; + + private openDropdown = () => { this.open = !this.open; + if (this.open) { + this.el.addEventListener("calciteDropdownOpen", this.toggleOpenEnd); + } }; private updateTabIndexOfItems(target: HTMLCalciteDropdownItemElement): void { From de0c3eb1364a00c9bf098fe8c71b71eb73b4e154 Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Wed, 25 Sep 2024 00:34:38 -0500 Subject: [PATCH 7/9] avoid closing dropdown on arrow keys --- .../src/components/dropdown/dropdown.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.tsx b/packages/calcite-components/src/components/dropdown/dropdown.tsx index dce92da1b95..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.openDropdown(); + this.toggleDropdown(); } @Listen("pointerleave") @@ -588,12 +588,12 @@ export class Dropdown } if (isActivationKey(key)) { - this.openDropdown(); + this.toggleDropdown(); event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { this.focusLastDropdownItem = key === "ArrowUp"; - this.openDropdown(); - event.preventDefault(); + this.open = true; + this.el.addEventListener("calciteDropdownOpen", this.onOpenEnd); } }; @@ -652,15 +652,15 @@ export class Dropdown focusElement(target); }; - private toggleOpenEnd = (): void => { + private onOpenEnd = (): void => { this.focusOnFirstActiveOrDefaultItem(); - this.el.removeEventListener("calciteDropdownOpen", this.toggleOpenEnd); + this.el.removeEventListener("calciteDropdownOpen", this.onOpenEnd); }; - private openDropdown = () => { + private toggleDropdown = () => { this.open = !this.open; if (this.open) { - this.el.addEventListener("calciteDropdownOpen", this.toggleOpenEnd); + this.el.addEventListener("calciteDropdownOpen", this.onOpenEnd); } }; From 6b08f1d5b4c5d7afa3e685f467a66e4cf6cc9568 Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Wed, 25 Sep 2024 09:33:16 -0500 Subject: [PATCH 8/9] add delay for open story --- .../src/components/dropdown/dropdown.stories.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.stories.ts b/packages/calcite-components/src/components/dropdown/dropdown.stories.ts index f668a560e9c..c3d10eb2255 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.stories.ts +++ b/packages/calcite-components/src/components/dropdown/dropdown.stories.ts @@ -495,3 +495,5 @@ export const openInAllScales = (): string => html`
`; + +openInAllScales.parameters = { chromatic: { delay: 1000 } }; From 59836d46a78dc671732a46891bc346e664f9903d Mon Sep 17 00:00:00 2001 From: Anveshreddy mekala Date: Wed, 25 Sep 2024 09:55:10 -0500 Subject: [PATCH 9/9] revert delay for open story --- .../src/components/dropdown/dropdown.stories.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/calcite-components/src/components/dropdown/dropdown.stories.ts b/packages/calcite-components/src/components/dropdown/dropdown.stories.ts index c3d10eb2255..f668a560e9c 100644 --- a/packages/calcite-components/src/components/dropdown/dropdown.stories.ts +++ b/packages/calcite-components/src/components/dropdown/dropdown.stories.ts @@ -495,5 +495,3 @@ export const openInAllScales = (): string => html`
`; - -openInAllScales.parameters = { chromatic: { delay: 1000 } };