From 062df5ec851a1e54eca36e0179ecef4598b537b3 Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Mon, 3 Nov 2025 15:25:11 +0200 Subject: [PATCH 1/2] feat(ui5-icon): implement accessibilityInfo getter --- packages/main/cypress/specs/Icon.cy.tsx | 62 ++++++++++++++++++++++++- packages/main/src/Icon.ts | 11 +++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/main/cypress/specs/Icon.cy.tsx b/packages/main/cypress/specs/Icon.cy.tsx index 687ed2e00069..5b2f791f6608 100644 --- a/packages/main/cypress/specs/Icon.cy.tsx +++ b/packages/main/cypress/specs/Icon.cy.tsx @@ -274,4 +274,64 @@ describe("Icon general interaction", () => { .find(".ui5-icon-root") .should("have.attr", "role", "img"); }); -}); \ No newline at end of file + + it("Tests accessibilityInfo getter", () => { + const interactiveMode = "Interactive"; + const imageMode = "Image"; + const decorativeMode = "Decorative"; + const accessibleName = "Test Icon"; + + // Test with Interactive mode + cy.mount( + + ); + + cy.get("[ui5-icon][mode='Interactive']").then($icon => { + const icon = $icon[0] as any; + const accessibilityInfo = icon.accessibilityInfo; + + // For Interactive mode, accessibilityInfo should have role and description + expect(accessibilityInfo).to.not.be.undefined; + expect(accessibilityInfo.role).to.equal("button"); + expect(accessibilityInfo.description).to.equal(accessibleName); + }); + + // Test with Decorative mode + cy.mount( + + ); + + cy.get("[ui5-icon][mode='Decorative']").then($icon => { + const icon = $icon[0] as any; + const accessibilityInfo = icon.accessibilityInfo; + + // For Decorative mode, accessibilityInfo should be undefined + expect(accessibilityInfo).to.be.undefined; + }); + + // Test with Image mode + cy.mount( + + ); + + cy.get("[ui5-icon][mode='Image']").then($icon => { + const icon = $icon[0] as any; + const accessibilityInfo = icon.accessibilityInfo; + + // For Image mode, accessibilityInfo should be undefined + expect(accessibilityInfo).to.be.undefined; + }); + }); +}); diff --git a/packages/main/src/Icon.ts b/packages/main/src/Icon.ts index a44406ef717b..a032234d3adb 100644 --- a/packages/main/src/Icon.ts +++ b/packages/main/src/Icon.ts @@ -3,6 +3,7 @@ import jsxRender from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import type { AriaRole } from "@ui5/webcomponents-base/dist/types.js"; import type { IconData, UnsafeIconData } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js"; import { getIconData, getIconDataSync } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js"; import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; @@ -327,6 +328,16 @@ class Icon extends UI5Element implements IIcon { get hasIconTooltip() { return this.showTooltip && this.effectiveAccessibleName; } + + get accessibilityInfo() { + if (this.mode === IconMode.Interactive) { + return { + role: this.effectiveAccessibleRole as AriaRole, + description: this.effectiveAccessibleName, + }; + } + return undefined; + } } Icon.define(); From f5259adeb0b926733a5524514404ad9b01fd512d Mon Sep 17 00:00:00 2001 From: Iliana Bobeva Date: Tue, 18 Nov 2025 13:05:05 +0200 Subject: [PATCH 2/2] chore: add translatable type in accessibilityInfo and return undefined only for decorative mode --- packages/main/cypress/specs/Icon.cy.tsx | 10 ++++-- packages/main/src/Icon.ts | 33 +++++++++++++++---- .../main/src/i18n/messagebundle.properties | 6 ++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/main/cypress/specs/Icon.cy.tsx b/packages/main/cypress/specs/Icon.cy.tsx index 5b2f791f6608..0eb1bddf06c5 100644 --- a/packages/main/cypress/specs/Icon.cy.tsx +++ b/packages/main/cypress/specs/Icon.cy.tsx @@ -294,9 +294,10 @@ describe("Icon general interaction", () => { const icon = $icon[0] as any; const accessibilityInfo = icon.accessibilityInfo; - // For Interactive mode, accessibilityInfo should have role and description + // For Interactive mode, accessibilityInfo should have role, type and description expect(accessibilityInfo).to.not.be.undefined; expect(accessibilityInfo.role).to.equal("button"); + expect(accessibilityInfo.type).to.equal("Button"); expect(accessibilityInfo.description).to.equal(accessibleName); }); @@ -330,8 +331,11 @@ describe("Icon general interaction", () => { const icon = $icon[0] as any; const accessibilityInfo = icon.accessibilityInfo; - // For Image mode, accessibilityInfo should be undefined - expect(accessibilityInfo).to.be.undefined; + // For Image mode, accessibilityInfo should have role, type and description + expect(accessibilityInfo).to.not.be.undefined; + expect(accessibilityInfo.role).to.equal("img"); + expect(accessibilityInfo.type).to.equal("Image"); + expect(accessibilityInfo.description).to.equal(accessibleName); }); }); }); diff --git a/packages/main/src/Icon.ts b/packages/main/src/Icon.ts index a032234d3adb..8d0c91fc1261 100644 --- a/packages/main/src/Icon.ts +++ b/packages/main/src/Icon.ts @@ -8,6 +8,8 @@ import type { IconData, UnsafeIconData } from "@ui5/webcomponents-base/dist/asse import { getIconData, getIconDataSync } from "@ui5/webcomponents-base/dist/asset-registries/Icons.js"; import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type { I18nText } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js"; import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js"; import executeTemplate from "@ui5/webcomponents-base/dist/renderer/executeTemplate.js"; @@ -15,6 +17,8 @@ import IconTemplate from "./IconTemplate.js"; import type IconDesign from "./types/IconDesign.js"; import IconMode from "./types/IconMode.js"; +import { ICON_ARIA_TYPE_IMAGE, ICON_ARIA_TYPE_INTERACTIVE } from "./generated/i18n/i18n-defaults.js"; + // Styles import iconCss from "./generated/themes/Icon.css.js"; @@ -118,6 +122,10 @@ class Icon extends UI5Element implements IIcon { eventDetails!: { click: void } + + @i18n("@ui5/webcomponents") + static i18nBundle: I18nBundle; + /** * Defines the component semantic design. * @default "Default" @@ -329,14 +337,27 @@ class Icon extends UI5Element implements IIcon { return this.showTooltip && this.effectiveAccessibleName; } + _getAriaTypeDescription() { + switch (this.mode) { + case IconMode.Interactive: + return Icon.i18nBundle.getText(ICON_ARIA_TYPE_INTERACTIVE); + case IconMode.Image: + return Icon.i18nBundle.getText(ICON_ARIA_TYPE_IMAGE); + default: + return ""; + } + } + get accessibilityInfo() { - if (this.mode === IconMode.Interactive) { - return { - role: this.effectiveAccessibleRole as AriaRole, - description: this.effectiveAccessibleName, - }; + if (this.mode === IconMode.Decorative) { + return undefined; } - return undefined; + + return { + role: this.effectiveAccessibleRole as AriaRole, + type: this._getAriaTypeDescription(), + description: this.effectiveAccessibleName, + }; } } diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 91e1e9198f9b..b69860c27eae 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -924,3 +924,9 @@ DYNAMIC_DATE_RANGE_NEXT_COMBINED_TEXT=Next X {0} (included) #XFLD: Suffix text for included date range options. DYNAMIC_DATE_RANGE_INCLUDED_TEXT=(included) + +#XACT: ARIA announcement for icon type image +ICON_ARIA_TYPE_IMAGE=Image + +#XACT: ARIA announcement for icon type interactive +ICON_ARIA_TYPE_INTERACTIVE=Button \ No newline at end of file