From e70e85c4c7c1a84c3422992dc17dfc8c289cc286 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Fri, 17 Apr 2026 16:26:58 +0300 Subject: [PATCH 1/4] feat: add semantic click --- .../main/cypress/specs/ColorPalette.cy.tsx | 246 ++++++++++++++++++ packages/main/src/ColorPalette.ts | 35 +-- packages/main/src/ColorPaletteItem.ts | 59 +++++ .../main/src/ColorPaletteItemTemplate.tsx | 1 + packages/main/src/ColorPalettePopover.ts | 2 +- packages/main/test/pages/ColorPalette.html | 64 +++++ 6 files changed, 391 insertions(+), 16 deletions(-) diff --git a/packages/main/cypress/specs/ColorPalette.cy.tsx b/packages/main/cypress/specs/ColorPalette.cy.tsx index aacd8c6e1c4b..4eedae7bb008 100644 --- a/packages/main/cypress/specs/ColorPalette.cy.tsx +++ b/packages/main/cypress/specs/ColorPalette.cy.tsx @@ -312,3 +312,249 @@ describe("Color Palette Item - tooltip", () => { .and("not.contain", "#d60d5a"); }); }); + +describe("Color Palette Item: click event", () => { + it("should fire item-click event when item is clicked", () => { + const clickSpy = cy.spy().as("clickSpy"); + const itemClickSpy = cy.spy().as("itemClickSpy"); + + cy.mount( + + + + + ); + + cy.get("[ui5-color-palette]") + .then($el => { + $el[0].addEventListener("item-click", itemClickSpy); + }); + + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", clickSpy); + }); + + cy.get("#item2") + .realClick(); + + cy.get("@clickSpy") + .should("have.been.calledOnce"); + + cy.get("@itemClickSpy") + .should("have.been.calledOnce"); + }); + + it("should prevent selection when preventDefault is called", () => { + cy.mount( + + + + + ); + + cy.get("[ui5-color-palette]") + .then($el => { + $el[0].addEventListener("item-click", cy.spy().as("itemClickSpy")); + }); + + // First, select item1 + cy.get("#item1") + .realClick(); + + cy.get("#item1") + .should("have.attr", "selected"); + cy.get("#item2") + .should("not.have.attr", "selected"); + + // Now add preventDefault to item2 + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", (e: Event) => { + e.preventDefault(); + }); + }); + + // Try to click item2 with preventDefault + cy.get("#item2") + .realClick(); + + // Item1 should still be selected because we called preventDefault on item2 + cy.get("#item1") + .should("have.attr", "selected"); + cy.get("#item2") + .should("not.have.attr", "selected"); + + // The item-click event on ColorPalette should only have been called once (for item1) + cy.get("@itemClickSpy") + .should("have.been.calledOnce"); + }); + + it("should provide correct modifier keys in click event detail", () => { + cy.mount( + + + + + ); + + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", cy.spy((e: CustomEvent) => { + // Check that event detail contains modifier keys + expect(e.detail).to.have.property("altKey"); + expect(e.detail).to.have.property("ctrlKey"); + expect(e.detail).to.have.property("metaKey"); + expect(e.detail).to.have.property("shiftKey"); + + // Initially all should be false + expect(e.detail.altKey).to.be.false; + expect(e.detail.ctrlKey).to.be.false; + expect(e.detail.metaKey).to.be.false; + expect(e.detail.shiftKey).to.be.false; + }).as("clickSpy")); + }); + + cy.get("#item2") + .realClick(); + + cy.get("@clickSpy") + .should("have.been.calledOnce"); + }); + + it("should provide correct modifier keys when Ctrl is pressed", () => { + let eventDetail: any; + + cy.mount( + + + + + ); + + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", (e: Event) => { + eventDetail = (e as CustomEvent).detail; + }); + }); + + // Manually dispatch a MouseEvent with ctrlKey + cy.get("#item2") + .shadow() + .find(".ui5-cp-item") + .then($item => { + const mouseEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + ctrlKey: true, + altKey: false, + metaKey: false, + shiftKey: false + }); + $item[0].dispatchEvent(mouseEvent); + }); + + cy.then(() => eventDetail) + .then((detail) => { + expect(detail, "event detail should exist").to.exist; + expect(detail.ctrlKey, "ctrlKey should be true").to.be.true; + expect(detail.altKey, "altKey should be false").to.be.false; + expect(detail.metaKey, "metaKey should be false").to.be.false; + expect(detail.shiftKey, "shiftKey should be false").to.be.false; + }); + }); + + it("should provide correct modifier keys when Alt is pressed", () => { + cy.mount( + + + + + ); + + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", cy.spy((e: CustomEvent) => { + expect(e.detail.altKey).to.be.true; + expect(e.detail.ctrlKey).to.be.false; + expect(e.detail.metaKey).to.be.false; + expect(e.detail.shiftKey).to.be.false; + }).as("clickSpyAlt")); + }); + + cy.get("#item2") + .realClick({ altKey: true }); + + cy.get("@clickSpyAlt") + .should("have.been.calledOnce"); + }); + + it("should provide correct modifier keys when Shift is pressed", () => { + cy.mount( + + + + + ); + + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", cy.spy((e: CustomEvent) => { + expect(e.detail.shiftKey).to.be.true; + expect(e.detail.altKey).to.be.false; + expect(e.detail.ctrlKey).to.be.false; + expect(e.detail.metaKey).to.be.false; + }).as("clickSpyShift")); + }); + + cy.get("#item2") + .realClick({ shiftKey: true }); + + cy.get("@clickSpyShift") + .should("have.been.calledOnce"); + }); + + it("should provide correct modifier keys when multiple modifiers are pressed", () => { + let eventDetail: any; + + cy.mount( + + + + + ); + + cy.get("#item2") + .then($el => { + $el[0].addEventListener("click", (e: Event) => { + eventDetail = (e as CustomEvent).detail; + }); + }); + + // Manually dispatch a MouseEvent with multiple modifier keys + cy.get("#item2") + .shadow() + .find(".ui5-cp-item") + .then($item => { + const mouseEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + ctrlKey: true, + altKey: false, + metaKey: false, + shiftKey: true + }); + $item[0].dispatchEvent(mouseEvent); + }); + + cy.then(() => eventDetail) + .then((detail) => { + expect(detail, "event detail should exist").to.exist; + expect(detail.ctrlKey, "ctrlKey should be true").to.be.true; + expect(detail.shiftKey, "shiftKey should be true").to.be.true; + expect(detail.altKey, "altKey should be false").to.be.false; + expect(detail.metaKey, "metaKey should be false").to.be.false; + }); + }); +}); diff --git a/packages/main/src/ColorPalette.ts b/packages/main/src/ColorPalette.ts index 30787ef8aa1b..128b7886de25 100644 --- a/packages/main/src/ColorPalette.ts +++ b/packages/main/src/ColorPalette.ts @@ -285,7 +285,7 @@ class ColorPalette extends UI5Element { item.focus(); - if (this.displayedColors.includes(item)) { + if (this.displayedColors.includes(item as unknown as IColorPaletteItem)) { this._itemNavigation.setCurrentItem(item); } @@ -312,7 +312,7 @@ class ColorPalette extends UI5Element { let colorItems: IColorPaletteItem[] = this.colors; if (this.popupMode) { - colorItems = this.getSlottedNodes("colors"); + colorItems = this.getSlottedNodes("colors") as unknown as IColorPaletteItem[]; } return colorItems; @@ -323,7 +323,7 @@ class ColorPalette extends UI5Element { * @private */ _ensureSingleSelectionOrDeselectAll() { - let lastSelectedItem: IColorPaletteItem; + let lastSelectedItem: IColorPaletteItem | ColorPaletteItem | undefined; this.allColorsInPalette.forEach(item => { if (item.selected) { @@ -336,6 +336,11 @@ class ColorPalette extends UI5Element { } _onclick(e: MouseEvent) { + if (e.defaultPrevented) { + e.preventDefault(); + return; + } + this.handleSelection(e.target as ColorPaletteItem); } @@ -348,7 +353,7 @@ class ColorPalette extends UI5Element { const colorItem = target as ColorPaletteItem; - if (this.displayedColors.includes(colorItem)) { + if (this.displayedColors.includes(colorItem as unknown as IColorPaletteItem)) { this._itemNavigation.setCurrentItem(colorItem); } else if (this.recentColorsElements.includes(colorItem)) { this._itemNavigationRecentColors.setCurrentItem(colorItem); @@ -616,7 +621,7 @@ class ColorPalette extends UI5Element { return false; } - return this.displayedColors.includes(this._currentlySelected) + return this.displayedColors.includes(this._currentlySelected as unknown as IColorPaletteItem) || this.recentColorsElements.includes(this._currentlySelected); } @@ -628,12 +633,12 @@ class ColorPalette extends UI5Element { return isDown(e) || isRight(e); } - _isFirstSwatch(target: ColorPaletteItem, swatches: Array): boolean { - return swatches && Boolean(swatches.length) && swatches[0] === target; + _isFirstSwatch(target: ColorPaletteItem, swatches: Array | Array): boolean { + return swatches && Boolean(swatches.length) && swatches[0] === (target as any); } - _isLastSwatch(target: ColorPaletteItem, swatches: Array): boolean { - return swatches && Boolean(swatches.length) && swatches[swatches.length - 1] === target; + _isLastSwatch(target: ColorPaletteItem, swatches: Array | Array): boolean { + return swatches && Boolean(swatches.length) && swatches[swatches.length - 1] === (target as any); } /** @@ -641,7 +646,7 @@ class ColorPalette extends UI5Element { * @private */ _isFirstSwatchInRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target); + const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); return index >= 0 ? index % this.rowSize === 0 : false; } @@ -650,7 +655,7 @@ class ColorPalette extends UI5Element { * @private */ _isLastSwatchInRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target); + const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); return index >= 0 ? (index + 1) % this.rowSize === 0 || index === this.displayedColors.length - 1 : false; } @@ -666,7 +671,7 @@ class ColorPalette extends UI5Element { * @returns True if the swatch is the last of the last full row, false otherwise. */ _isLastSwatchOfLastFullRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target); + const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); const rowSize = this.rowSize; const total = this.displayedColors.length; const lastCompleteRowEndIndex = this._getLastCompleteRowEndIndex(total, rowSize); @@ -674,7 +679,7 @@ class ColorPalette extends UI5Element { } _isSwatchInLastRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target); + const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); const lastRowSwatchesCount = this.displayedColors.length % this.rowSize; return index >= 0 && index >= this.displayedColors.length - lastRowSwatchesCount; } @@ -799,7 +804,7 @@ class ColorPalette extends UI5Element { */ _focusFirstRecentColor(): boolean { if (this.hasRecentColors && this.recentColorsElements.length) { - this.focusColorElement(this.recentColorsElements[0], this._itemNavigationRecentColors); + this.focusColorElement(this.recentColorsElements[0] as unknown as IColorPaletteItem, this._itemNavigationRecentColors); return true; } return false; @@ -811,7 +816,7 @@ class ColorPalette extends UI5Element { */ _focusLastRecentColor(): boolean { if (this.hasRecentColors && this.recentColorsElements.length) { - this.focusColorElement(this.recentColorsElements[this.recentColorsElements.length - 1], this._itemNavigationRecentColors); + this.focusColorElement(this.recentColorsElements[this.recentColorsElements.length - 1] as unknown as IColorPaletteItem, this._itemNavigationRecentColors); return true; } return false; diff --git a/packages/main/src/ColorPaletteItem.ts b/packages/main/src/ColorPaletteItem.ts index 486b727adf3b..875d206f350a 100644 --- a/packages/main/src/ColorPaletteItem.ts +++ b/packages/main/src/ColorPaletteItem.ts @@ -10,10 +10,18 @@ import ColorPaletteItemTemplate from "./ColorPaletteItemTemplate.js"; import { COLORPALETTE_COLOR_LABEL, } from "./generated/i18n/i18n-defaults.js"; +import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; // Styles import ColorPaletteItemCss from "./generated/themes/ColorPaletteItem.css.js"; +type ColorPaletteItemNativeClickEventDetail = { + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + shiftKey: boolean; +}; + /** * @class * @@ -33,7 +41,27 @@ import ColorPaletteItemCss from "./generated/themes/ColorPaletteItem.css.js"; template: ColorPaletteItemTemplate, shadowRootOptions: { delegatesFocus: true }, }) + +/** + * Fired when the component is activated either with a mouse/tap or by using the Enter or Space key. + * + * **Note:** The event will not be fired if the `disabled` property is set to `true`. + * + * @param {boolean} altKey Returns whether the "ALT" key was pressed when the event was triggered. + * @param {boolean} ctrlKey Returns whether the "CTRL" key was pressed when the event was triggered. + * @param {boolean} metaKey Returns whether the "META" key was pressed when the event was triggered. + * @param {boolean} shiftKey Returns whether the "SHIFT" key was pressed when the event was triggered. + * @since 2.22.0 + * @public + */ +@event("click", { + bubbles: true, + cancelable: true, +}) class ColorPaletteItem extends UI5Element implements IColorPaletteItem { + eventDetails!: { + "click": ColorPaletteItemNativeClickEventDetail, + } /** * Defines the colour of the component. * @@ -129,8 +157,39 @@ class ColorPaletteItem extends UI5Element implements IColorPaletteItem { }, }; } + + _onClick(e: MouseEvent) { + if (this._disabled) { + e.preventDefault(); + e.stopPropagation(); + return; + } + + e.stopImmediatePropagation(); + + const { + altKey, + ctrlKey, + metaKey, + shiftKey, + } = e; + + // Fire semantic click event (CustomEvent that bubbles) + const prevented = !this.fireDecoratorEvent("click", { + altKey, + ctrlKey, + metaKey, + shiftKey, + }); + + if (prevented) { + e.preventDefault(); + e.stopPropagation(); + } + } } ColorPaletteItem.define(); export default ColorPaletteItem; +export type { ColorPaletteItemNativeClickEventDetail }; diff --git a/packages/main/src/ColorPaletteItemTemplate.tsx b/packages/main/src/ColorPaletteItemTemplate.tsx index f8dae34c0604..f1ab9a326f9d 100644 --- a/packages/main/src/ColorPaletteItemTemplate.tsx +++ b/packages/main/src/ColorPaletteItemTemplate.tsx @@ -9,6 +9,7 @@ export default function ColorPaletteItemTemplate(this: ColorPaletteItem) { aria-label={this.getLabelText} aria-pressed={this.selected} title={this.getLabelText} + onClick={this._onClick} > ); } diff --git a/packages/main/src/ColorPalettePopover.ts b/packages/main/src/ColorPalettePopover.ts index b9a8d3042eb5..39d8588d2f20 100644 --- a/packages/main/src/ColorPalettePopover.ts +++ b/packages/main/src/ColorPalettePopover.ts @@ -204,7 +204,7 @@ class ColorPalettePopover extends UI5Element { } // since height is dynamically determined by padding-block-start - colorPalette.allColorsInPalette.forEach((item: IColorPaletteItem) => { + (colorPalette.allColorsInPalette as unknown as Array).forEach((item: IColorPaletteItem) => { const itemHeight = item.offsetHeight + 4; // adding 4px for the offsets on top and bottom item.style.setProperty("--_ui5_color_palette_item_height", `${itemHeight}px`); }); diff --git a/packages/main/test/pages/ColorPalette.html b/packages/main/test/pages/ColorPalette.html index b961217011f9..1060efb2b9f7 100644 --- a/packages/main/test/pages/ColorPalette.html +++ b/packages/main/test/pages/ColorPalette.html @@ -127,6 +127,35 @@ + +
+
+
+

ColorPaletteItem Click Event Demo

+

Sample 1: Listen to click event on individual items (with modifier keys)

+ +
+ + + + + + + + +
+
+
+

Sample 2: preventDefault on item click prevents parent's item-click event

+ +
+ + + + + +

The middle item (brown) calls preventDefault and parent's item-click will not fire

+ From af517745ee6896464b0c8784ffccf5886d470951 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 20 Apr 2026 11:44:43 +0300 Subject: [PATCH 2/4] refactor: remove casting --- packages/main/src/ColorPalette.ts | 38 +++++++++++------------- packages/main/src/ColorPalettePopover.ts | 2 +- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/main/src/ColorPalette.ts b/packages/main/src/ColorPalette.ts index 128b7886de25..1e782696acb8 100644 --- a/packages/main/src/ColorPalette.ts +++ b/packages/main/src/ColorPalette.ts @@ -54,7 +54,7 @@ interface IColorPaletteItem extends UI5Element, ITabbable { selected?: boolean, } -type ColorPaletteNavigationItem = IColorPaletteItem | Button; +type ColorPaletteNavigationItem = ColorPaletteItem | Button; type ColorPaletteItemClickEventDetail = { color: string, @@ -203,7 +203,7 @@ class ColorPalette extends UI5Element { invalidateOnChildChange: true, individualSlots: true, }) - colors!: DefaultSlot; + colors!: DefaultSlot; _itemNavigation: ItemNavigation; _itemNavigationRecentColors: ItemNavigation; @@ -285,7 +285,7 @@ class ColorPalette extends UI5Element { item.focus(); - if (this.displayedColors.includes(item as unknown as IColorPaletteItem)) { + if (this.displayedColors.includes(item)) { this._itemNavigation.setCurrentItem(item); } @@ -308,14 +308,11 @@ class ColorPalette extends UI5Element { }); } - get effectiveColorItems() { - let colorItems: IColorPaletteItem[] = this.colors; - + get effectiveColorItems(): ColorPaletteItem[] { if (this.popupMode) { - colorItems = this.getSlottedNodes("colors") as unknown as IColorPaletteItem[]; + return this.getSlottedNodes("colors"); } - - return colorItems; + return this.colors; } /** @@ -323,7 +320,7 @@ class ColorPalette extends UI5Element { * @private */ _ensureSingleSelectionOrDeselectAll() { - let lastSelectedItem: IColorPaletteItem | ColorPaletteItem | undefined; + let lastSelectedItem: ColorPaletteItem | undefined; this.allColorsInPalette.forEach(item => { if (item.selected) { @@ -337,7 +334,6 @@ class ColorPalette extends UI5Element { _onclick(e: MouseEvent) { if (e.defaultPrevented) { - e.preventDefault(); return; } @@ -353,7 +349,7 @@ class ColorPalette extends UI5Element { const colorItem = target as ColorPaletteItem; - if (this.displayedColors.includes(colorItem as unknown as IColorPaletteItem)) { + if (this.displayedColors.includes(colorItem)) { this._itemNavigation.setCurrentItem(colorItem); } else if (this.recentColorsElements.includes(colorItem)) { this._itemNavigationRecentColors.setCurrentItem(colorItem); @@ -621,7 +617,7 @@ class ColorPalette extends UI5Element { return false; } - return this.displayedColors.includes(this._currentlySelected as unknown as IColorPaletteItem) + return this.displayedColors.includes(this._currentlySelected) || this.recentColorsElements.includes(this._currentlySelected); } @@ -646,7 +642,7 @@ class ColorPalette extends UI5Element { * @private */ _isFirstSwatchInRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); + const index = this.displayedColors.indexOf(target); return index >= 0 ? index % this.rowSize === 0 : false; } @@ -655,7 +651,7 @@ class ColorPalette extends UI5Element { * @private */ _isLastSwatchInRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); + const index = this.displayedColors.indexOf(target); return index >= 0 ? (index + 1) % this.rowSize === 0 || index === this.displayedColors.length - 1 : false; } @@ -671,7 +667,7 @@ class ColorPalette extends UI5Element { * @returns True if the swatch is the last of the last full row, false otherwise. */ _isLastSwatchOfLastFullRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); + const index = this.displayedColors.indexOf(target); const rowSize = this.rowSize; const total = this.displayedColors.length; const lastCompleteRowEndIndex = this._getLastCompleteRowEndIndex(total, rowSize); @@ -679,7 +675,7 @@ class ColorPalette extends UI5Element { } _isSwatchInLastRow(target: ColorPaletteItem): boolean { - const index = this.displayedColors.indexOf(target as unknown as IColorPaletteItem); + const index = this.displayedColors.indexOf(target); const lastRowSwatchesCount = this.displayedColors.length % this.rowSize; return index >= 0 && index >= this.displayedColors.length - lastRowSwatchesCount; } @@ -804,7 +800,7 @@ class ColorPalette extends UI5Element { */ _focusFirstRecentColor(): boolean { if (this.hasRecentColors && this.recentColorsElements.length) { - this.focusColorElement(this.recentColorsElements[0] as unknown as IColorPaletteItem, this._itemNavigationRecentColors); + this.focusColorElement(this.recentColorsElements[0], this._itemNavigationRecentColors); return true; } return false; @@ -816,7 +812,7 @@ class ColorPalette extends UI5Element { */ _focusLastRecentColor(): boolean { if (this.hasRecentColors && this.recentColorsElements.length) { - this.focusColorElement(this.recentColorsElements[this.recentColorsElements.length - 1] as unknown as IColorPaletteItem, this._itemNavigationRecentColors); + this.focusColorElement(this.recentColorsElements[this.recentColorsElements.length - 1], this._itemNavigationRecentColors); return true; } return false; @@ -901,8 +897,8 @@ class ColorPalette extends UI5Element { return this._selectedColor; } - get displayedColors(): Array { - const colors = this.getSlottedNodes("colors"); + get displayedColors(): Array { + const colors = this.getSlottedNodes("colors"); return colors.filter(item => item.value).slice(0, 15); } diff --git a/packages/main/src/ColorPalettePopover.ts b/packages/main/src/ColorPalettePopover.ts index 39d8588d2f20..212e4fcefd74 100644 --- a/packages/main/src/ColorPalettePopover.ts +++ b/packages/main/src/ColorPalettePopover.ts @@ -204,7 +204,7 @@ class ColorPalettePopover extends UI5Element { } // since height is dynamically determined by padding-block-start - (colorPalette.allColorsInPalette as unknown as Array).forEach((item: IColorPaletteItem) => { + (colorPalette.allColorsInPalette).forEach((item: ColorPaletteItem) => { const itemHeight = item.offsetHeight + 4; // adding 4px for the offsets on top and bottom item.style.setProperty("--_ui5_color_palette_item_height", `${itemHeight}px`); }); From 732e377819e4597ddc84a417820dc08e79ee6083 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Thu, 23 Apr 2026 15:23:02 +0300 Subject: [PATCH 3/4] refactor: update event details --- .../main/cypress/specs/ColorPalette.cy.tsx | 60 +++++++++++-------- packages/main/src/ColorPaletteItem.ts | 25 ++------ packages/main/test/pages/ColorPalette.html | 11 ++-- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/packages/main/cypress/specs/ColorPalette.cy.tsx b/packages/main/cypress/specs/ColorPalette.cy.tsx index 4eedae7bb008..910bfd656da6 100644 --- a/packages/main/cypress/specs/ColorPalette.cy.tsx +++ b/packages/main/cypress/specs/ColorPalette.cy.tsx @@ -401,17 +401,19 @@ describe("Color Palette Item: click event", () => { cy.get("#item2") .then($el => { $el[0].addEventListener("click", cy.spy((e: CustomEvent) => { - // Check that event detail contains modifier keys - expect(e.detail).to.have.property("altKey"); - expect(e.detail).to.have.property("ctrlKey"); - expect(e.detail).to.have.property("metaKey"); - expect(e.detail).to.have.property("shiftKey"); + // Check that event detail contains item and originalEvent + expect(e.detail).to.have.property("item"); + expect(e.detail).to.have.property("originalEvent"); - // Initially all should be false - expect(e.detail.altKey).to.be.false; - expect(e.detail.ctrlKey).to.be.false; - expect(e.detail.metaKey).to.be.false; - expect(e.detail.shiftKey).to.be.false; + // Check item properties + expect(e.detail.item.value).to.equal("blue"); + + // Check modifier keys from originalEvent + const originalEvent = e.detail.originalEvent; + expect(originalEvent.altKey).to.be.false; + expect(originalEvent.ctrlKey).to.be.false; + expect(originalEvent.metaKey).to.be.false; + expect(originalEvent.shiftKey).to.be.false; }).as("clickSpy")); }); @@ -458,10 +460,12 @@ describe("Color Palette Item: click event", () => { cy.then(() => eventDetail) .then((detail) => { expect(detail, "event detail should exist").to.exist; - expect(detail.ctrlKey, "ctrlKey should be true").to.be.true; - expect(detail.altKey, "altKey should be false").to.be.false; - expect(detail.metaKey, "metaKey should be false").to.be.false; - expect(detail.shiftKey, "shiftKey should be false").to.be.false; + expect(detail.item.value, "item value should be blue").to.equal("blue"); + const originalEvent = detail.originalEvent; + expect(originalEvent.ctrlKey, "ctrlKey should be true").to.be.true; + expect(originalEvent.altKey, "altKey should be false").to.be.false; + expect(originalEvent.metaKey, "metaKey should be false").to.be.false; + expect(originalEvent.shiftKey, "shiftKey should be false").to.be.false; }); }); @@ -476,10 +480,11 @@ describe("Color Palette Item: click event", () => { cy.get("#item2") .then($el => { $el[0].addEventListener("click", cy.spy((e: CustomEvent) => { - expect(e.detail.altKey).to.be.true; - expect(e.detail.ctrlKey).to.be.false; - expect(e.detail.metaKey).to.be.false; - expect(e.detail.shiftKey).to.be.false; + const originalEvent = e.detail.originalEvent; + expect(originalEvent.altKey).to.be.true; + expect(originalEvent.ctrlKey).to.be.false; + expect(originalEvent.metaKey).to.be.false; + expect(originalEvent.shiftKey).to.be.false; }).as("clickSpyAlt")); }); @@ -501,10 +506,11 @@ describe("Color Palette Item: click event", () => { cy.get("#item2") .then($el => { $el[0].addEventListener("click", cy.spy((e: CustomEvent) => { - expect(e.detail.shiftKey).to.be.true; - expect(e.detail.altKey).to.be.false; - expect(e.detail.ctrlKey).to.be.false; - expect(e.detail.metaKey).to.be.false; + const originalEvent = e.detail.originalEvent; + expect(originalEvent.shiftKey).to.be.true; + expect(originalEvent.altKey).to.be.false; + expect(originalEvent.ctrlKey).to.be.false; + expect(originalEvent.metaKey).to.be.false; }).as("clickSpyShift")); }); @@ -551,10 +557,12 @@ describe("Color Palette Item: click event", () => { cy.then(() => eventDetail) .then((detail) => { expect(detail, "event detail should exist").to.exist; - expect(detail.ctrlKey, "ctrlKey should be true").to.be.true; - expect(detail.shiftKey, "shiftKey should be true").to.be.true; - expect(detail.altKey, "altKey should be false").to.be.false; - expect(detail.metaKey, "metaKey should be false").to.be.false; + expect(detail.item.value, "item value should be blue").to.equal("blue"); + const originalEvent = detail.originalEvent; + expect(originalEvent.ctrlKey, "ctrlKey should be true").to.be.true; + expect(originalEvent.shiftKey, "shiftKey should be true").to.be.true; + expect(originalEvent.altKey, "altKey should be false").to.be.false; + expect(originalEvent.metaKey, "metaKey should be false").to.be.false; }); }); }); diff --git a/packages/main/src/ColorPaletteItem.ts b/packages/main/src/ColorPaletteItem.ts index 875d206f350a..272a5527d1fe 100644 --- a/packages/main/src/ColorPaletteItem.ts +++ b/packages/main/src/ColorPaletteItem.ts @@ -16,10 +16,8 @@ import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import ColorPaletteItemCss from "./generated/themes/ColorPaletteItem.css.js"; type ColorPaletteItemNativeClickEventDetail = { - altKey: boolean; - ctrlKey: boolean; - metaKey: boolean; - shiftKey: boolean; + item: ColorPaletteItem, + originalEvent: Event; }; /** @@ -47,10 +45,8 @@ type ColorPaletteItemNativeClickEventDetail = { * * **Note:** The event will not be fired if the `disabled` property is set to `true`. * - * @param {boolean} altKey Returns whether the "ALT" key was pressed when the event was triggered. - * @param {boolean} ctrlKey Returns whether the "CTRL" key was pressed when the event was triggered. - * @param {boolean} metaKey Returns whether the "META" key was pressed when the event was triggered. - * @param {boolean} shiftKey Returns whether the "SHIFT" key was pressed when the event was triggered. + * @param {ColorPaletteItem} item The color palette item that was clicked. + * @param {Event} originalEvent The original DOM event that triggered the click. Use this to access modifier keys (altKey, ctrlKey, metaKey, shiftKey) and other native event properties. * @since 2.22.0 * @public */ @@ -167,19 +163,10 @@ class ColorPaletteItem extends UI5Element implements IColorPaletteItem { e.stopImmediatePropagation(); - const { - altKey, - ctrlKey, - metaKey, - shiftKey, - } = e; - // Fire semantic click event (CustomEvent that bubbles) const prevented = !this.fireDecoratorEvent("click", { - altKey, - ctrlKey, - metaKey, - shiftKey, + item: this, + originalEvent: e, }); if (prevented) { diff --git a/packages/main/test/pages/ColorPalette.html b/packages/main/test/pages/ColorPalette.html index 1060efb2b9f7..7e83135bdd60 100644 --- a/packages/main/test/pages/ColorPalette.html +++ b/packages/main/test/pages/ColorPalette.html @@ -175,14 +175,15 @@

ColorPaletteItem Click Event Demo

// Sample 1: Individual item click events with modifier keys [item1, item2, item3, item4, item5].forEach(function(item) { item.addEventListener("click", function(event) { + const originalEvent = event.detail.originalEvent; const modifiers = []; - if (event.detail.altKey) modifiers.push("Alt"); - if (event.detail.ctrlKey) modifiers.push("Ctrl"); - if (event.detail.metaKey) modifiers.push("Meta"); - if (event.detail.shiftKey) modifiers.push("Shift"); + if (originalEvent.altKey) modifiers.push("Alt"); + if (originalEvent.ctrlKey) modifiers.push("Ctrl"); + if (originalEvent.metaKey) modifiers.push("Meta"); + if (originalEvent.shiftKey) modifiers.push("Shift"); const modifierText = modifiers.length > 0 ? " with " + modifiers.join("+") : ""; - itemClickEventResult.value = "Clicked " + item.value + modifierText; + itemClickEventResult.value = "Clicked item: " + item.value + " (event.detail.item.value: " + event.detail.item.value + ")" + modifierText; }); }); From eb549561ae7726dd2cbf572185fae6510118b0c2 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Fri, 24 Apr 2026 15:02:16 +0300 Subject: [PATCH 4/4] fix: remove casting --- packages/main/src/ColorPalette.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/main/src/ColorPalette.ts b/packages/main/src/ColorPalette.ts index 1e782696acb8..474a9607f710 100644 --- a/packages/main/src/ColorPalette.ts +++ b/packages/main/src/ColorPalette.ts @@ -629,12 +629,12 @@ class ColorPalette extends UI5Element { return isDown(e) || isRight(e); } - _isFirstSwatch(target: ColorPaletteItem, swatches: Array | Array): boolean { - return swatches && Boolean(swatches.length) && swatches[0] === (target as any); + _isFirstSwatch(target: ColorPaletteItem, swatches: Array): boolean { + return swatches && Boolean(swatches.length) && swatches[0] === (target); } - _isLastSwatch(target: ColorPaletteItem, swatches: Array | Array): boolean { - return swatches && Boolean(swatches.length) && swatches[swatches.length - 1] === (target as any); + _isLastSwatch(target: ColorPaletteItem, swatches: Array): boolean { + return swatches && Boolean(swatches.length) && swatches[swatches.length - 1] === (target); } /**