diff --git a/packages/main/cypress/specs/ColorPalettePopover.cy.tsx b/packages/main/cypress/specs/ColorPalettePopover.cy.tsx
new file mode 100644
index 000000000000..24dfe038319f
--- /dev/null
+++ b/packages/main/cypress/specs/ColorPalettePopover.cy.tsx
@@ -0,0 +1,133 @@
+import Button from "../../src/Button.js";
+import ColorPalettePopover from "../../src/ColorPalettePopover.js";
+import ColorPaletteItem from "../../src/ColorPaletteItem.js";
+
+describe("Color Popover Palette tests", () => {
+ describe("Home and End keyboard navigation", () => {
+ it("showDefaultColor & showMoreColors", () => {
+ cy.mount(<>
+
+
+
+
+
+
+
+ >);
+
+ cy.get("[ui5-color-palette-popover]")
+ .ui5PaletteOpen();
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Default Color");
+
+ cy.focused()
+ .realPress("End");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "More Colors...");
+
+ cy.focused()
+ .realPress("Home");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Default Color");
+ });
+
+ it("showDefaultColor", () => {
+ cy.mount(<>
+
+
+
+
+
+
+
+ >);
+
+ cy.get("[ui5-color-palette-popover]")
+ .ui5PaletteOpen();
+
+ cy.focused()
+ .realPress("End");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 4: red");
+
+ cy.focused()
+ .realPress("Home");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 1: cyan");
+
+ cy.focused()
+ .realPress("Home");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Default Color");
+ });
+
+ it("showMoreColors", () => {
+ cy.mount(<>
+
+
+
+
+
+
+
+ >);
+
+ cy.get("[ui5-color-palette-popover]")
+ .ui5PaletteOpen();
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 1: cyan");
+
+ cy.focused()
+ .realPress("End");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 4: red");
+
+ cy.focused()
+ .realPress("End");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "More Colors...");
+ });
+
+ it("Item navigation End", () => {
+ cy.mount(<>
+
+
+
+
+
+
+
+
+
+
+ >);
+
+ cy.get("[ui5-color-palette-popover]")
+ .ui5PaletteOpen();
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 1: cyan");
+
+ cy.focused()
+ .realPress("ArrowDown");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 6: purple");
+
+ cy.focused()
+ .realPress("End");
+
+ cy.focused()
+ .should("have.attr", "aria-label", "Color - 7: red");
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/main/cypress/support/commands.ts b/packages/main/cypress/support/commands.ts
index ec83e0f4d043..c74d0c439ed2 100644
--- a/packages/main/cypress/support/commands.ts
+++ b/packages/main/cypress/support/commands.ts
@@ -43,6 +43,7 @@ import { ModifierKey } from "./commands/common/types.js";
// Please keep this list in alphabetical order
import "./commands/Calendar.commands.js";
import "./commands/ColorPalette.commands.js";
+import "./commands/ColorPalettePopover.commands.ts";
import "./commands/ColorPicker.commands.js";
import "./commands/DateTimePicker.commands.js";
import "./commands/DateRangePicker.commands.js";
diff --git a/packages/main/cypress/support/commands/ColorPalettePopover.commands.ts b/packages/main/cypress/support/commands/ColorPalettePopover.commands.ts
new file mode 100644
index 000000000000..72e280b5b82e
--- /dev/null
+++ b/packages/main/cypress/support/commands/ColorPalettePopover.commands.ts
@@ -0,0 +1,44 @@
+Cypress.Commands.add("ui5PaletteOpen", { prevSubject: true }, (prevSubject, options) => {
+ cy.wrap(prevSubject)
+ .as("palette")
+ .then($palette => {
+ if (options?.opener) {
+ cy.wrap($palette)
+ .invoke("attr", "opener", options.opener);
+ }
+
+ cy.wrap($palette)
+ .invoke("attr", "open", true);
+ });
+
+ cy.get("@palette")
+ .ui5PaletteOpened();
+});
+
+Cypress.Commands.add("ui5PaletteOpened", { prevSubject: true }, subject => {
+ cy.wrap(subject)
+ .as("palette");
+
+ cy.get("@palette")
+ .should("have.attr", "open");
+
+ cy.get("@palette")
+ .shadow()
+ .find("[ui5-responsive-popover]")
+ .should($rp => {
+ expect($rp.is(":popover-open")).to.be.true;
+ expect($rp.width()).to.not.equal(0);
+ expect($rp.height()).to.not.equal(0);
+ })
+ .and("have.attr", "open");
+});
+
+
+declare global {
+ namespace Cypress {
+ interface Chainable {
+ ui5PaletteOpen(options?: { opener?: string }): Chainable
+ ui5PaletteOpened(): Chainable
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/main/src/ColorPalette.ts b/packages/main/src/ColorPalette.ts
index c6ca6b5c12a4..fc4253b41105 100644
--- a/packages/main/src/ColorPalette.ts
+++ b/packages/main/src/ColorPalette.ts
@@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
+import query from "@ui5/webcomponents-base/dist/decorators/query.js";
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
@@ -17,6 +18,8 @@ import {
isDown,
isUp,
isTabNext,
+ isHome,
+ isEnd,
} from "@ui5/webcomponents-base/dist/Keys.js";
import ColorPaletteTemplate from "./ColorPaletteTemplate.js";
import type ColorPaletteItem from "./ColorPaletteItem.js";
@@ -187,6 +190,12 @@ class ColorPalette extends UI5Element {
_currentlySelected?: ColorPaletteItem;
_shouldFocusRecentColors = false;
+ @query(".ui5-cp-default-color-button")
+ _defaultColorButton!: Button;
+
+ @query(".ui5-cp-more-colors")
+ _moreColorsButton!: Button;
+
@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;
@@ -309,6 +318,16 @@ class ColorPalette extends UI5Element {
this.handleSelection(e.target as ColorPaletteItem);
}
+ _onmousedown(e: MouseEvent) {
+ const target = e.target as ColorPaletteItem;
+
+ if (this.displayedColors.includes(target)) {
+ this._itemNavigation.setCurrentItem(target);
+ } else if (this.recentColorsElements.includes(target)) {
+ this._itemNavigationRecentColors.setCurrentItem(target);
+ }
+ }
+
_onkeyup(e: KeyboardEvent) {
const target = e.target as ColorPaletteItem;
if (isSpace(e)) {
@@ -326,6 +345,11 @@ class ColorPalette extends UI5Element {
if (isSpace(e)) {
e.preventDefault();
}
+
+ if (!this.popupMode && (isHome(e) || isEnd(e))) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
}
handleSelection(target: ColorPaletteItem) {
@@ -393,6 +417,14 @@ class ColorPalette extends UI5Element {
this.focusColorElement(this.displayedColors[colorPaletteFocusIndex], this._itemNavigation);
}
+ } else if (isEnd(e)) {
+ e.stopPropagation();
+
+ if (this.showMoreColors) {
+ this._moreColorsButton?.focus();
+ } else if (this.displayedColors.length) {
+ this.focusColorElement(this.displayedColors[this.displayedColors.length - 1], this._itemNavigation);
+ }
}
}
@@ -415,6 +447,14 @@ class ColorPalette extends UI5Element {
} else {
this.focusColorElement(this.displayedColors[0], this._itemNavigation);
}
+ } else if (isHome(e)) {
+ e.stopPropagation();
+
+ if (this.showDefaultColor) {
+ this._defaultColorButton?.focus();
+ } else if (this.displayedColors.length) {
+ this.focusColorElement(this.displayedColors[0], this._itemNavigation);
+ }
}
}
@@ -435,7 +475,7 @@ class ColorPalette extends UI5Element {
this.selectColor(target);
}
- if (isUp(e) && target === this.displayedColors[0] && this.colorPaletteNavigationElements.length > 1) {
+ if (isUp(e) && this._isFirstDisplayedSwatch(target) && this.colorPaletteNavigationElements.length > 1) {
e.stopPropagation();
if (this.showDefaultColor) {
this.firstFocusableElement.focus();
@@ -444,7 +484,7 @@ class ColorPalette extends UI5Element {
} else if (!this.showDefaultColor && this.showMoreColors) {
lastElementInNavigation.focus();
}
- } else if (isDown(e) && target === this.displayedColors[this.displayedColors.length - 1] && this.colorPaletteNavigationElements.length > 1) {
+ } else if (isDown(e) && this._isLastDisplayedSwatch(target) && this.colorPaletteNavigationElements.length > 1) {
e.stopPropagation();
const isRecentColorsNextElement = (this.showDefaultColor && !this.showMoreColors && this.hasRecentColors) || (!this.showDefaultColor && !this.showMoreColors && this.hasRecentColors);
@@ -457,9 +497,32 @@ class ColorPalette extends UI5Element {
} else if (!this.showDefaultColor && this.showMoreColors) {
this.colorPaletteNavigationElements[1].focus();
}
+ } else if (isHome(e) && this.showDefaultColor && this._isFirstDisplayedSwatch(target)) {
+ e.stopPropagation();
+ this._defaultColorButton?.focus();
+ } else if (isEnd(e) && this.showMoreColors && this._isLastDisplayedSwatch(target)) {
+ e.stopPropagation();
+ this._moreColorsButton?.focus();
+ } else if (isEnd(e) && this._isElementInLastRow(target)) {
+ e.stopPropagation();
+ this.focusColorElement(this.displayedColors[this.displayedColors.length - 1], this._itemNavigation);
}
}
+ _isFirstDisplayedSwatch(target: ColorPaletteItem) {
+ return this.displayedColors[0] === target;
+ }
+
+ _isLastDisplayedSwatch(target: ColorPaletteItem) {
+ return this.displayedColors[this.displayedColors.length - 1] === target;
+ }
+
+ _isElementInLastRow(target: ColorPaletteItem) {
+ const index = this.displayedColors.indexOf(target);
+ const length = this.displayedColors.length;
+ return index >= length - (length % this.rowSize);
+ }
+
_onRecentColorsContainerKeyDown(e: KeyboardEvent) {
if (this._isUpOrDownNavigatableColorPaletteItem(e)) {
this._currentlySelected = undefined;
diff --git a/packages/main/src/ColorPaletteTemplate.tsx b/packages/main/src/ColorPaletteTemplate.tsx
index 7d726ac84f1e..38030e8911fb 100644
--- a/packages/main/src/ColorPaletteTemplate.tsx
+++ b/packages/main/src/ColorPaletteTemplate.tsx
@@ -15,6 +15,7 @@ export default function ColorPaletteTemplate(this: ColorPalette) {
onClick={this._onclick}
onKeyUp={this._onkeyup}
onKeyDown={this._onkeydown}
+ onMouseDown={this._onmousedown}
>
{this.showDefaultColor &&