diff --git a/packages/components/src/components/card-group/card-group.tsx b/packages/components/src/components/card-group/card-group.tsx index e89da72507a..c02b5068e23 100644 --- a/packages/components/src/components/card-group/card-group.tsx +++ b/packages/components/src/components/card-group/card-group.tsx @@ -1,6 +1,6 @@ import { PropertyValues } from "lit"; import { createRef } from "lit/directives/ref.js"; -import { LitElement, property, createEvent, h, method, JsxNode, ToEvents } from "@arcgis/lumina"; +import { LitElement, property, createEvent, h, method, JsxNode } from "@arcgis/lumina"; import { focusElementInGroup } from "../../utils/dom"; import { Scale, SelectionMode } from "../interfaces"; import type { Card } from "../card/card"; @@ -92,10 +92,7 @@ export class CardGroup extends LitElement { constructor() { super(); - this.listen["calciteInternalCardKeyEvent"]>( - "calciteInternalCardKeyEvent", - this.calciteInternalCardKeyEventListener, - ); + this.listen("keydown", this.keyDownHandler); this.listen("calciteCardSelect", this.calciteCardSelectListener); } @@ -121,29 +118,35 @@ export class CardGroup extends LitElement { //#region Private Methods - private calciteInternalCardKeyEventListener(event: CustomEvent): void { - if (event.composedPath().includes(this.el)) { - const interactiveItems = this.items.filter((el) => !el.disabled); - switch (event.detail["key"]) { - case "ArrowRight": - focusElementInGroup(interactiveItems, event.target as Card["el"], "next", true, false); - break; - case "ArrowLeft": - focusElementInGroup( - interactiveItems, - event.target as Card["el"], - "previous", - true, - false, - ); - break; - case "Home": - focusElementInGroup(interactiveItems, event.target as Card["el"], "first", true, false); - break; - case "End": - focusElementInGroup(interactiveItems, event.target as Card["el"], "last", true, false); - break; - } + private keyDownHandler(event: KeyboardEvent): void { + if (this.disabled || !event.composedPath().includes(this.el)) { + return; + } + + const card = this.items.find((item) => item === event.target); + + if (!card || card.disabled || card.selectable) { + return; + } + + const interactiveItems = this.items.filter((el) => !el.disabled); + switch (event.key) { + case "ArrowRight": + focusElementInGroup(interactiveItems, card, "next", true, false); + event.preventDefault(); + break; + case "ArrowLeft": + focusElementInGroup(interactiveItems, card, "previous", true, false); + event.preventDefault(); + break; + case "Home": + focusElementInGroup(interactiveItems, card, "first", true, false); + event.preventDefault(); + break; + case "End": + focusElementInGroup(interactiveItems, card, "last", true, false); + event.preventDefault(); + break; } } diff --git a/packages/components/src/components/card/card.tsx b/packages/components/src/components/card/card.tsx index 6307b782bfa..ade643b2fb7 100644 --- a/packages/components/src/components/card/card.tsx +++ b/packages/components/src/components/card/card.tsx @@ -145,9 +145,6 @@ export class Card extends LitElement { /** Fires when the deprecated `selectable` is true, or `selectionMode` set on parent `calcite-card-group` is not `none` and the component is selected. */ calciteCardSelect = createEvent({ cancelable: false }); - /** @private */ - calciteInternalCardKeyEvent = createEvent({ cancelable: false }); - //#endregion //#region Private Methods @@ -181,16 +178,6 @@ export class Card extends LitElement { if (isActivationKey(event.key) && this.selectionMode !== "none") { this.calciteCardSelect.emit(); event.preventDefault(); - } else { - switch (event.key) { - case "ArrowRight": - case "ArrowLeft": - case "Home": - case "End": - this.calciteInternalCardKeyEvent.emit(event); - event.preventDefault(); - break; - } } } } diff --git a/packages/components/src/components/chip-group/chip-group.tsx b/packages/components/src/components/chip-group/chip-group.tsx index 8c7296586d8..2049910a8ce 100644 --- a/packages/components/src/components/chip-group/chip-group.tsx +++ b/packages/components/src/components/chip-group/chip-group.tsx @@ -104,7 +104,7 @@ export class ChipGroup extends LitElement { constructor() { super(); - this.listen("calciteInternalChipKeyEvent", this.calciteInternalChipKeyEventListener); + this.listen("keydown", this.keyDownHandler); this.listen("calciteChipClose", this.calciteChipCloseListener); this.listen("calciteChipSelect", this.calciteChipSelectListener); this.listen("calciteInternalChipSelect", this.calciteInternalChipSelectListener); @@ -125,22 +125,29 @@ export class ChipGroup extends LitElement { //#region Private Methods - private calciteInternalChipKeyEventListener(event: CustomEvent): void { - if (event.composedPath().includes(this.el)) { - const destinationFromKey: Record = { - ArrowRight: "next", - ArrowLeft: "previous", - Home: "first", - End: "last", - }; - const destination = destinationFromKey[event.detail.key]; - - if (destination) { - const interactiveItems = this.items?.filter((el) => !el.disabled); - focusElementInGroup(interactiveItems, event.detail.target, destination, true, true, true); - } + private keyDownHandler(event: KeyboardEvent): void { + const destinationFromKey: Record = { + ArrowRight: "next", + ArrowLeft: "previous", + Home: "first", + End: "last", + }; + const destination = destinationFromKey[event.key]; + + if (!destination) { + return; } - event.stopPropagation(); + + const chip = event + .composedPath() + .find((el): el is Chip["el"] => el instanceof HTMLElement && el.matches("calcite-chip")); + + if (!chip || !this.items?.includes(chip)) { + return; + } + + const interactiveItems = this.items?.filter((el) => !el.disabled); + focusElementInGroup(interactiveItems, chip, destination, true, true, true); } private calciteChipCloseListener(event: CustomEvent): void { diff --git a/packages/components/src/components/chip/chip.tsx b/packages/components/src/components/chip/chip.tsx index 7bb175c7b02..553278d62c2 100644 --- a/packages/components/src/components/chip/chip.tsx +++ b/packages/components/src/components/chip/chip.tsx @@ -166,9 +166,6 @@ export class Chip extends LitElement { /** Fires when the selected state of the component changes. */ calciteChipSelect = createEvent({ cancelable: false }); - /** @private */ - calciteInternalChipKeyEvent = createEvent({ cancelable: false }); - /** @private */ calciteInternalChipSelect = createEvent({ cancelable: false }); @@ -237,7 +234,6 @@ export class Chip extends LitElement { case "ArrowLeft": case "Home": case "End": - this.calciteInternalChipKeyEvent.emit(event); event.preventDefault(); break; } diff --git a/packages/components/src/components/menu-item/interfaces.ts b/packages/components/src/components/menu-item/interfaces.ts index 8c862aa3064..fb42dddf2ef 100644 --- a/packages/components/src/components/menu-item/interfaces.ts +++ b/packages/components/src/components/menu-item/interfaces.ts @@ -1,9 +1 @@ -import type { MenuItem } from "./menu-item"; - -export interface MenuItemCustomEvent { - event: KeyboardEvent; - children?: MenuItem["el"][]; - isSubmenuOpen?: boolean; -} - export type Layout = "horizontal" | "vertical"; diff --git a/packages/components/src/components/menu-item/menu-item.tsx b/packages/components/src/components/menu-item/menu-item.tsx index d852d73a015..999a9461b71 100644 --- a/packages/components/src/components/menu-item/menu-item.tsx +++ b/packages/components/src/components/menu-item/menu-item.tsx @@ -18,7 +18,6 @@ import { useT9n } from "../../controllers/useT9n"; import type { Action } from "../action/action"; import { useSetFocus } from "../../controllers/useSetFocus"; import { CSS, SLOTS, ICONS } from "./resources"; -import { MenuItemCustomEvent } from "./interfaces"; import T9nStrings from "./assets/t9n/messages.en.json"; import { styles } from "./menu-item.scss"; @@ -144,9 +143,6 @@ export class MenuItem extends LitElement { //#region Events - /** @private */ - calciteInternalMenuItemKeyEvent = createEvent(); - /** Emits when the component is selected. */ calciteMenuItemSelect = createEvent(); @@ -219,7 +215,7 @@ export class MenuItem extends LitElement { } private async keyDownHandler(event: KeyboardEvent): Promise { - const { hasSubmenu, href, layout, open, submenuItems } = this; + const { hasSubmenu, href, layout, open } = this; const key = event.key; const targetIsDropdown = event.target === this.dropdownActionRef.value; @@ -240,39 +236,21 @@ export class MenuItem extends LitElement { } else if (key === "Escape") { if (open) { this.open = false; + event.preventDefault(); return; } - this.calciteInternalMenuItemKeyEvent.emit({ event }); - event.preventDefault(); } else if (key === "ArrowDown" || key === "ArrowUp") { - event.preventDefault(); if ((targetIsDropdown || !href) && hasSubmenu && !open && layout === "horizontal") { this.open = true; + event.preventDefault(); return; } - this.calciteInternalMenuItemKeyEvent.emit({ - event, - children: submenuItems, - isSubmenuOpen: open && hasSubmenu, - }); - } else if (key === "ArrowLeft") { - event.preventDefault(); - this.calciteInternalMenuItemKeyEvent.emit({ - event, - children: submenuItems, - isSubmenuOpen: true, - }); } else if (key === "ArrowRight") { - event.preventDefault(); if ((targetIsDropdown || !href) && hasSubmenu && !open && layout === "vertical") { this.open = true; + event.preventDefault(); return; } - this.calciteInternalMenuItemKeyEvent.emit({ - event, - children: submenuItems, - isSubmenuOpen: open && hasSubmenu, - }); } } diff --git a/packages/components/src/components/menu/menu.browser.e2e.tsx b/packages/components/src/components/menu/menu.browser.e2e.tsx index 8100890db11..8965216d78c 100644 --- a/packages/components/src/components/menu/menu.browser.e2e.tsx +++ b/packages/components/src/components/menu/menu.browser.e2e.tsx @@ -1,6 +1,7 @@ import { h } from "@arcgis/lumina"; -import { describe } from "vitest"; +import { describe, expect, it } from "vitest"; import { mount } from "@arcgis/lumina-compiler/testing"; +import { userEvent } from "vitest/browser"; import { focusable, hidden, renders, t9n, accessible } from "../../tests/commonTests/browser"; describe("accessible", () => { @@ -51,6 +52,27 @@ describe("focusable", () => { ); }); +describe("keyboard navigation", () => { + it("bubbles native keydown events and only prevents handled keys", async () => { + const { el } = await mount<"calcite-menu">( + + + , + ); + const item = el.querySelector("calcite-menu-item")!; + const keydownEvents: KeyboardEvent[] = []; + + el.parentElement!.addEventListener("keydown", (event) => keydownEvents.push(event)); + await item.setFocus(); + + await userEvent.keyboard("{ArrowRight}"); + expect(keydownEvents.at(-1)?.defaultPrevented).toBe(true); + + await userEvent.keyboard("a"); + expect(keydownEvents.at(-1)?.defaultPrevented).toBe(false); + }); +}); + describe("translation support", () => { t9n(() => mount("calcite-menu")); }); diff --git a/packages/components/src/components/menu/menu.tsx b/packages/components/src/components/menu/menu.tsx index 8edb364cd47..b953ad9ff60 100644 --- a/packages/components/src/components/menu/menu.tsx +++ b/packages/components/src/components/menu/menu.tsx @@ -79,7 +79,7 @@ export class Menu extends LitElement { constructor() { super(); - this.listen("calciteInternalMenuItemKeyEvent", this.calciteInternalNavMenuItemKeyEvent); + this.listen("keydown", this.calciteInternalNavMenuItemKeyEvent); } override willUpdate(changes: PropertyValues): void { @@ -101,48 +101,79 @@ export class Menu extends LitElement { this.setMenuItemLayout(this.menuItems, this.layout); } - private calciteInternalNavMenuItemKeyEvent(event: CustomEvent): void { - const target = event.target as MenuItem["el"]; - const submenuItems = event.detail.children; - const key = event.detail.event.key; - event.stopPropagation(); + private calciteInternalNavMenuItemKeyEvent(event: KeyboardEvent): void { + if (event.defaultPrevented) { + return; + } + + const target = this.getMenuItemFromEvent(event); + + if (!target) { + return; + } + + const submenuItems = this.getSubmenuItems(target); + const hasSubmenu = submenuItems.length > 0; + const key = event.key; + let handled = false; if (key === "ArrowDown") { if (target.layout === "vertical") { focusElementInGroup(this.menuItems, target, "next", false, false); - } else { - if (event.detail.isSubmenuOpen) { - submenuItems[0].setFocus(); - } + handled = true; + } else if (target.open && hasSubmenu) { + submenuItems[0].setFocus(); + handled = true; } } else if (key === "ArrowUp") { - if (this.layout === "vertical") { + if (target.layout === "vertical") { focusElementInGroup(this.menuItems, target, "previous", false, false); - } else { - if (event.detail.isSubmenuOpen) { - submenuItems[submenuItems.length - 1].setFocus(); - } + handled = true; + } else if (target.open && hasSubmenu) { + submenuItems[submenuItems.length - 1].setFocus(); + handled = true; } } else if (key === "ArrowRight") { if (this.layout === "horizontal") { focusElementInGroup(this.menuItems, target, "next", false, false); - } else { - if (event.detail.isSubmenuOpen) { - submenuItems[0].setFocus(); - } + handled = true; + } else if (target.open && hasSubmenu) { + submenuItems[0].setFocus(); + handled = true; } } else if (key === "ArrowLeft") { if (this.layout === "horizontal") { focusElementInGroup(this.menuItems, target, "previous", false, false); - } else { - if (event.detail.isSubmenuOpen) { - this.focusParentElement(event.target as MenuItem["el"]); - } + handled = true; + } else if (target.parentElement?.tagName === "CALCITE-MENU-ITEM") { + this.focusParentElement(target); + handled = true; } - } else if (key === "Escape") { - this.focusParentElement(event.target as MenuItem["el"]); + } else if (key === "Escape" && target.parentElement?.tagName === "CALCITE-MENU-ITEM") { + this.focusParentElement(target); + handled = true; + } + + if (handled) { + event.preventDefault(); } - event.preventDefault(); + } + + private getMenuItemFromEvent(event: KeyboardEvent): MenuItem["el"] | undefined { + const target = event + .composedPath() + .find( + (node): node is MenuItem["el"] => + node instanceof HTMLElement && node.tagName === "CALCITE-MENU-ITEM", + ); + + return target && this.menuItems.includes(target) ? target : undefined; + } + + private getSubmenuItems(menuItem: MenuItem["el"]): MenuItem["el"][] { + return Array.from(menuItem.children).filter((child): child is MenuItem["el"] => + child.matches('calcite-menu-item[slot="submenu-item"]'), + ); } private handleMenuSlotChange(event: Event): void { diff --git a/packages/components/src/components/stepper-item/stepper-item.tsx b/packages/components/src/components/stepper-item/stepper-item.tsx index 89fede6dc66..2e0db436243 100644 --- a/packages/components/src/components/stepper-item/stepper-item.tsx +++ b/packages/components/src/components/stepper-item/stepper-item.tsx @@ -14,7 +14,6 @@ import { Scale } from "../interfaces"; import { StepperItemChangeEventDetail, StepperItemEventDetail, - StepperItemKeyEventDetail, StepperLayout, } from "../stepper/interfaces"; import { NumberingSystem, numberStringFormatter } from "../../utils/locale"; @@ -162,11 +161,6 @@ export class StepperItem extends LitElement { //#region Events - /** @private */ - calciteInternalStepperItemKeyEvent = createEvent({ - cancelable: false, - }); - /** @private */ calciteInternalStepperItemUpdate = createEvent({ cancelable: false }); @@ -265,7 +259,6 @@ export class StepperItem extends LitElement { case "ArrowRight": case "Home": case "End": - this.calciteInternalStepperItemKeyEvent.emit({ item: event }); event.preventDefault(); break; } diff --git a/packages/components/src/components/stepper/interfaces.ts b/packages/components/src/components/stepper/interfaces.ts index 83051ad73d4..35472ec1305 100644 --- a/packages/components/src/components/stepper/interfaces.ts +++ b/packages/components/src/components/stepper/interfaces.ts @@ -2,10 +2,6 @@ export interface StepperItemEventDetail { position: number; } -export interface StepperItemKeyEventDetail { - item: KeyboardEvent; -} - export interface StepperItemChangeEventDetail { position: number; } diff --git a/packages/components/src/components/stepper/stepper.browser.e2e.tsx b/packages/components/src/components/stepper/stepper.browser.e2e.tsx index 7d419c43520..03c0d62f008 100644 --- a/packages/components/src/components/stepper/stepper.browser.e2e.tsx +++ b/packages/components/src/components/stepper/stepper.browser.e2e.tsx @@ -1,7 +1,7 @@ import { h, JsxNode } from "@arcgis/lumina"; import { describe, expect, it } from "vitest"; import { mount } from "@arcgis/lumina-compiler/testing"; -import { page } from "vitest/browser"; +import { page, userEvent } from "vitest/browser"; import { LitElement } from "@arcgis/lumina"; import { defaults, reflects, hidden, renders, t9n, themed } from "../../tests/commonTests/browser"; import { CSS as STEPPER_ITEM_CSS } from "../stepper-item/resources"; @@ -171,6 +171,59 @@ describe("inheritable props in shadow DOM", () => { }); }); +describe("keyboard navigation", () => { + async function mountStepper( + layout: Extract, + ): Promise { + await mount<"calcite-stepper">( + + + + + +
Step 2 content
+
+ +
Step 3 content
+
+
, + ); + } + + it("delegates horizontal keyboard navigation from stepper items", async () => { + await mountStepper("horizontal"); + + const firstItem = page.getBySelector("#step-1"); + const thirdItem = page.getBySelector("#step-3"); + const contentButton = page.getBySelector("#step-1-button"); + + await userEvent.click(contentButton); + await userEvent.keyboard("{ArrowRight}"); + await expect.element(contentButton).toHaveFocus(); + + await userEvent.keyboard("{Shift>}{Tab}{/Shift}"); + await expect.element(firstItem).toHaveFocus(); + + await userEvent.keyboard("{ArrowRight}"); + await expect.element(thirdItem).toHaveFocus(); + }); + + it("delegates vertical keyboard navigation from stepper items", async () => { + await mountStepper("vertical"); + + const firstItem = page.getBySelector("#step-1"); + const thirdItem = page.getBySelector("#step-3"); + + await userEvent.keyboard("{Tab}"); + await expect.element(firstItem).toHaveFocus(); + + await userEvent.keyboard("{ArrowDown}"); + await expect.element(thirdItem).toHaveFocus(); + }); +}); + describe("theme", () => { describe("horizontal-single", () => { themed( diff --git a/packages/components/src/components/stepper/stepper.tsx b/packages/components/src/components/stepper/stepper.tsx index 2229d33bca0..48e0aaa4fca 100644 --- a/packages/components/src/components/stepper/stepper.tsx +++ b/packages/components/src/components/stepper/stepper.tsx @@ -1,14 +1,5 @@ import { PropertyValues } from "lit"; -import { - createEvent, - h, - JsxNode, - LitElement, - method, - property, - state, - ToEvents, -} from "@arcgis/lumina"; +import { createEvent, h, JsxNode, LitElement, method, property, state } from "@arcgis/lumina"; import { createRef } from "lit/directives/ref.js"; import { focusElementInGroup, slotChangeGetAssignedElements } from "../../utils/dom"; import { Position, Scale } from "../interfaces"; @@ -20,11 +11,7 @@ import type { StepperItem } from "../stepper-item/stepper-item"; import type { Action } from "../action/action"; import { CSS, ICONS, IDS } from "./resources"; import { StepBar } from "./functional/step-bar"; -import { - StepperItemChangeEventDetail, - StepperItemKeyEventDetail, - StepperLayout, -} from "./interfaces"; +import { StepperItemChangeEventDetail, StepperLayout } from "./interfaces"; import T9nStrings from "./assets/t9n/messages.en.json"; import { styles } from "./stepper.scss"; import { isStepperItem } from "../stepper-item/resources"; @@ -190,10 +177,7 @@ export class Stepper extends LitElement { constructor() { super(); - this.listen["calciteInternalStepperItemKeyEvent"]>( - "calciteInternalStepperItemKeyEvent", - this.calciteInternalStepperItemKeyEvent, - ); + this.listen("keydown", this.keyDownHandler); this.listen("calciteInternalStepperItemUpdate", (event: Event): void => { event.stopPropagation(); this.updateItems(); @@ -249,27 +233,37 @@ export class Stepper extends LitElement { //#region Private Methods - private calciteInternalStepperItemKeyEvent(event: CustomEvent): void { - const item = event.detail.item; - const itemToFocus = event.target as StepperItem["el"]; + private keyDownHandler(event: KeyboardEvent): void { + if (!event.composedPath().includes(this.el)) { + return; + } + + const item = this.items.find((item) => item === event.target); + + if (!item || item.disabled) { + return; + } - switch (item.key) { + switch (event.key) { case "ArrowDown": case "ArrowRight": - focusElementInGroup(this.focusableItems, itemToFocus, "next"); + focusElementInGroup(this.focusableItems, item, "next"); + event.preventDefault(); break; case "ArrowUp": case "ArrowLeft": - focusElementInGroup(this.focusableItems, itemToFocus, "previous"); + focusElementInGroup(this.focusableItems, item, "previous"); + event.preventDefault(); break; case "Home": - focusElementInGroup(this.focusableItems, itemToFocus, "first"); + focusElementInGroup(this.focusableItems, item, "first"); + event.preventDefault(); break; case "End": - focusElementInGroup(this.focusableItems, itemToFocus, "last"); + focusElementInGroup(this.focusableItems, item, "last"); + event.preventDefault(); break; } - event.stopPropagation(); } private updateItem(event: CustomEvent): void { diff --git a/packages/components/src/components/swatch-group/swatch-group.tsx b/packages/components/src/components/swatch-group/swatch-group.tsx index 117ce4aa4a8..f812366dcde 100644 --- a/packages/components/src/components/swatch-group/swatch-group.tsx +++ b/packages/components/src/components/swatch-group/swatch-group.tsx @@ -99,7 +99,7 @@ export class SwatchGroup extends LitElement { constructor() { super(); - this.listen("calciteInternalSwatchKeyEvent", this.calciteInternalSwatchKeyEventListener); + this.listen("keydown", this.keyDownHandler); this.listen("calciteSwatchSelect", this.calciteSwatchSelectListener); this.listen("calciteInternalSwatchSelect", this.calciteInternalSwatchSelectListener); this.listen("calciteInternalSyncSelectedSwatches", this.calciteInternalSyncSelectedSwatches); @@ -115,25 +115,42 @@ export class SwatchGroup extends LitElement { //#region Private Methods - private calciteInternalSwatchKeyEventListener(event: CustomEvent): void { - if (event.composedPath().includes(this.el)) { - const interactiveItems = this.items?.filter((el) => !el.disabled); - switch (event.detail.key) { - case "ArrowRight": - focusElementInGroup(interactiveItems, event.detail.target, "next"); - break; - case "ArrowLeft": - focusElementInGroup(interactiveItems, event.detail.target, "previous"); - break; - case "Home": - focusElementInGroup(interactiveItems, event.detail.target, "first"); - break; - case "End": - focusElementInGroup(interactiveItems, event.detail.target, "last"); - break; - } + private keyDownHandler(event: KeyboardEvent): void { + const target = event + .composedPath() + .find( + (node): node is Swatch["el"] => + node instanceof HTMLElement && node.matches("calcite-swatch"), + ); + + if (!target || !this.el.contains(target)) { + return; + } + + const interactiveItems = this.items?.filter((el) => !el.disabled); + + if (!interactiveItems.includes(target)) { + return; + } + + switch (event.key) { + case "ArrowRight": + focusElementInGroup(interactiveItems, target, "next"); + event.preventDefault(); + break; + case "ArrowLeft": + focusElementInGroup(interactiveItems, target, "previous"); + event.preventDefault(); + break; + case "Home": + focusElementInGroup(interactiveItems, target, "first"); + event.preventDefault(); + break; + case "End": + focusElementInGroup(interactiveItems, target, "last"); + event.preventDefault(); + break; } - event.stopPropagation(); } private calciteSwatchSelectListener(event: CustomEvent): void { diff --git a/packages/components/src/components/swatch/swatch.tsx b/packages/components/src/components/swatch/swatch.tsx index c537e75fb94..c8ded93dbea 100644 --- a/packages/components/src/components/swatch/swatch.tsx +++ b/packages/components/src/components/swatch/swatch.tsx @@ -122,9 +122,6 @@ export class Swatch extends LitElement { //#region Events - /** @private */ - calciteInternalSwatchKeyEvent = createEvent({ cancelable: false }); - /** @private */ calciteInternalSwatchSelect = createEvent({ cancelable: false }); @@ -185,7 +182,6 @@ export class Swatch extends LitElement { case "ArrowLeft": case "Home": case "End": - this.calciteInternalSwatchKeyEvent.emit(event); event.preventDefault(); break; } diff --git a/packages/components/src/components/tile-group/tile-group.tsx b/packages/components/src/components/tile-group/tile-group.tsx index f90d274a999..df99328ca36 100644 --- a/packages/components/src/components/tile-group/tile-group.tsx +++ b/packages/components/src/components/tile-group/tile-group.tsx @@ -105,7 +105,7 @@ export class TileGroup extends LitElement implements SelectableGroupComponent { constructor() { super(); - this.listen("calciteInternalTileKeyEvent", this.calciteInternalTileKeyEventListener); + this.listen("keydown", this.keyDownHandler); this.listen("calciteTileSelect", this.calciteTileSelectHandler); } @@ -207,27 +207,38 @@ export class TileGroup extends LitElement implements SelectableGroupComponent { this.updateSelectedItems(); } - private calciteInternalTileKeyEventListener(event: CustomEvent): void { - if (event.composedPath().includes(this.el)) { - event.preventDefault(); - event.stopPropagation(); - const interactiveItems = this.items?.filter((el) => !el.disabled); - switch (event.detail.key) { - case "ArrowDown": - case "ArrowRight": - focusElementInGroup(interactiveItems, event.detail.target, "next", true, false); - break; - case "ArrowUp": - case "ArrowLeft": - focusElementInGroup(interactiveItems, event.detail.target, "previous", true, false); - break; - case "Home": - focusElementInGroup(interactiveItems, event.detail.target, "first", true, false); - break; - case "End": - focusElementInGroup(interactiveItems, event.detail.target, "last", true, false); - break; - } + private keyDownHandler(event: KeyboardEvent): void { + const composedPath = event.composedPath(); + if (this.disabled || !composedPath.includes(this.el)) { + return; + } + + const target = this.items.find((item) => item === event.target); + + if (!target || target.disabled) { + return; + } + + const interactiveItems = this.items?.filter((el) => !el.disabled); + switch (event.key) { + case "ArrowDown": + case "ArrowRight": + event.preventDefault(); + focusElementInGroup(interactiveItems, target, "next", true, false); + break; + case "ArrowUp": + case "ArrowLeft": + event.preventDefault(); + focusElementInGroup(interactiveItems, target, "previous", true, false); + break; + case "Home": + event.preventDefault(); + focusElementInGroup(interactiveItems, target, "first", true, false); + break; + case "End": + event.preventDefault(); + focusElementInGroup(interactiveItems, target, "last", true, false); + break; } } diff --git a/packages/components/src/components/tile/tile.tsx b/packages/components/src/components/tile/tile.tsx index bd9f31d8f1b..3f324a2790e 100644 --- a/packages/components/src/components/tile/tile.tsx +++ b/packages/components/src/components/tile/tile.tsx @@ -165,9 +165,6 @@ export class Tile extends LitElement implements SelectableComponent { // #region Events - /** @private */ - calciteInternalTileKeyEvent = createEvent({ cancelable: false }); - /** Fires when the selected state of the component changes. */ calciteTileSelect = createEvent(); @@ -219,15 +216,6 @@ export class Tile extends LitElement implements SelectableComponent { this.handleSelectEvent(); event.preventDefault(); break; - case "ArrowDown": - case "ArrowLeft": - case "ArrowRight": - case "ArrowUp": - case "Home": - case "End": - this.calciteInternalTileKeyEvent.emit(event); - event.preventDefault(); - break; } } }