From b61807961cc04c1aac6504760a69333ebddbe4a7 Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Mon, 13 Oct 2025 11:26:33 +0300 Subject: [PATCH 1/2] feat(ui5-textarea): support accessible description and description ref related to #12004 --- packages/main/cypress/specs/TextArea.cy.tsx | 76 +++++++++++++++++++++ packages/main/src/TextArea.ts | 39 ++++++++++- packages/main/src/TextAreaTemplate.tsx | 4 ++ packages/main/test/pages/TextArea.html | 10 +++ 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/packages/main/cypress/specs/TextArea.cy.tsx b/packages/main/cypress/specs/TextArea.cy.tsx index 7a3f1c3e9880..6db77dbdf060 100644 --- a/packages/main/cypress/specs/TextArea.cy.tsx +++ b/packages/main/cypress/specs/TextArea.cy.tsx @@ -133,6 +133,82 @@ describe("TextArea general interaction", () => { .find("textarea") .should("have.attr", "aria-label", attributeValue); }); + + it("Tests accessibleDescription property", () => { + const descriptionValue = "This is a direct description"; + + cy.mount(); + + cy.get("[ui5-textarea]") + .shadow() + .find("#accessibleDescription") + .should("contain.text", descriptionValue); + + cy.get("[ui5-textarea]") + .shadow() + .find("textarea") + .should("have.attr", "aria-describedby") + .and("include", "accessibleDescription"); + }); + + it("Tests accessibleDescriptionRef property", () => { + const descriptionValue = "External description text"; + + cy.mount( + <> +
{descriptionValue}
+ + + ); + + cy.get("[ui5-textarea]") + .shadow() + .find("#accessibleDescription") + .should("contain.text", descriptionValue); + + cy.get("[ui5-textarea]") + .shadow() + .find("textarea") + .should("have.attr", "aria-describedby") + .and("include", "accessibleDescription"); + }); + + it("Tests accessibleDescriptionRef with multiple references", () => { + const desc1 = "First description"; + const desc2 = "Second description"; + + cy.mount( + <> +
{desc1}
+
{desc2}
+ + + ); + + cy.get("[ui5-textarea]") + .shadow() + .find("#accessibleDescription") + .should("contain.text", desc1) + .and("contain.text", desc2); + }); + + it("Tests accessibleDescriptionRef takes precedence over accessibleDescription", () => { + const directDesc = "Direct description"; + const refDesc = "Referenced description"; + + cy.mount( + <> +
{refDesc}
+ + + ); + + cy.get("[ui5-textarea]") + .shadow() + .find("#accessibleDescription") + .should("contain.text", refDesc) + .and("not.contain.text", directDesc); + }); }); describe("disabled and readonly textarea", () => { diff --git a/packages/main/src/TextArea.ts b/packages/main/src/TextArea.ts index 02b03be5d940..e6564dc6a821 100644 --- a/packages/main/src/TextArea.ts +++ b/packages/main/src/TextArea.ts @@ -7,7 +7,11 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; -import { getEffectiveAriaLabelText, getAssociatedLabelForTexts } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; +import { + getEffectiveAriaLabelText, + getAssociatedLabelForTexts, + getEffectiveAriaDescriptionText, +} from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import { isEscape } from "@ui5/webcomponents-base/dist/Keys.js"; @@ -273,6 +277,24 @@ class TextArea extends UI5Element implements IFormInputElement { @property() accessibleNameRef?: string; + /** + * Defines the accessible description of the component. + * @default undefined + * @public + * @since 2.9.0 + */ + @property() + accessibleDescription?: string; + + /** + * Receives id(or many ids) of the elements that describe the textarea. + * @default undefined + * @public + * @since 2.9.0 + */ + @property() + accessibleDescriptionRef?: string; + /** * @private */ @@ -575,8 +597,21 @@ class TextArea extends UI5Element implements IFormInputElement { return effectiveAriaLabelText; } + get ariaDescriptionText() { + return getEffectiveAriaDescriptionText(this); + } + + get ariaDescriptionTextId() { + return this.ariaDescriptionText ? "accessibleDescription" : ""; + } + get ariaDescribedBy() { - return this.hasValueState ? `${this._id}-valueStateDesc` : undefined; + const ids = [ + this.hasValueState ? `${this._id}-valueStateDesc` : "", + this.ariaDescriptionTextId, + ].filter(Boolean).join(" "); + + return ids || undefined; } get ariaValueStateHiddenText() { diff --git a/packages/main/src/TextAreaTemplate.tsx b/packages/main/src/TextAreaTemplate.tsx index 728a4b6c3e2a..5dbe2e2a4a3a 100644 --- a/packages/main/src/TextAreaTemplate.tsx +++ b/packages/main/src/TextAreaTemplate.tsx @@ -51,6 +51,10 @@ export default function TextAreaTemplate(this: TextArea) { {this._exceededTextProps.exceededText} } + {this.ariaDescriptionText && + {this.ariaDescriptionText} + } + {this.hasValueState && {this.ariaValueStateHiddenText} } diff --git a/packages/main/test/pages/TextArea.html b/packages/main/test/pages/TextArea.html index ca7b9c0f5f72..aa000d6d2b1f 100644 --- a/packages/main/test/pages/TextArea.html +++ b/packages/main/test/pages/TextArea.html @@ -215,6 +215,16 @@ +
+ Text Area: Accessible Description +
This is an external description element
+
Additional description text
+ + + + +
+