From 45f0ffeafb2da0ffcaf425649c7440b604e359a3 Mon Sep 17 00:00:00 2001 From: Dobrin Dimchev Date: Tue, 12 Nov 2024 11:32:49 +0200 Subject: [PATCH] feat(ui5-list, ui5-tree): support accessible description (#10131) Related to: #6445 Description This PR adds support for the aria-describedby and the aria-description attribute to the ui5-list and ui5-tree components. These attributes allows developers to provide a reference to an element that describes the list or a string value, which can be read by screen readers. Example aria-description A property accessibleDescription is added to the ui5-list and ui5-tree components. When set, the value of this property will be used as the accessible description of the list. ... ... aria-describedby A property accessibleDescriptionRef is added to the ui5-list and ui5-tree components. When set, the value of this property will be used as the id of the element that describes the list.

This component has description

... ... Changes ui5-list and ui5-tree components now support the accessibleDescription and accessibleDescriptionRef properties An already existing utility named AriaLabelHelper was extended with a new methods getEffectiveAriaDescriptionText and getAllAccessibleDescriptionRefTexts to handle the new properties, similar to the ones for the aria-label attribute the name of the utility was changed to AccessibleTextsHelper to better reflect its purpose the ui5-list now subscribes for changes of the referenced elements using the AccessibleTextsHelper to update the aria-description and aria-label attribute of the list as well the ui5-tree only forwards the values to the internal ui5-tree-list which handles the property to attribute transformation --- docs/2-advanced/09-accessibility.md | 49 ++++++++++++++++ ...lHelper.ts => AccessibilityTextsHelper.ts} | 53 ++++++++++++++++-- packages/compat/src/Table.ts | 2 +- packages/fiori/src/IllustratedMessage.ts | 2 +- packages/main/cypress/specs/Tree.cy.ts | 29 ++++++++++ ...r.cy.ts => AccessibilityTextsHelper.cy.ts} | 38 ++++++++++++- packages/main/src/Button.ts | 2 +- packages/main/src/Card.ts | 2 +- packages/main/src/Carousel.ts | 2 +- packages/main/src/CheckBox.ts | 2 +- packages/main/src/ComboBox.ts | 2 +- packages/main/src/DatePicker.ts | 2 +- packages/main/src/Input.ts | 2 +- packages/main/src/Link.ts | 2 +- packages/main/src/List.hbs | 1 + packages/main/src/List.ts | 56 ++++++++++++++++++- packages/main/src/MultiComboBox.ts | 2 +- packages/main/src/Popup.ts | 2 +- packages/main/src/RadioButton.ts | 2 +- packages/main/src/RatingIndicator.ts | 2 +- packages/main/src/SegmentedButtonItem.ts | 2 +- packages/main/src/Select.ts | 2 +- packages/main/src/StepInput.ts | 2 +- packages/main/src/Switch.ts | 2 +- packages/main/src/Table.ts | 2 +- packages/main/src/TextArea.ts | 2 +- packages/main/src/TimePicker.ts | 2 +- packages/main/src/Tokenizer.ts | 2 +- packages/main/src/Toolbar.ts | 2 +- packages/main/src/Tree.hbs | 5 +- packages/main/src/Tree.ts | 23 ++++++-- packages/main/test/pages/List.html | 17 ++++++ packages/main/test/pages/Tree.html | 19 +++++++ ...per.html => AccessibilityTextsHelper.html} | 2 +- 34 files changed, 298 insertions(+), 40 deletions(-) rename packages/base/src/util/{AriaLabelHelper.ts => AccessibilityTextsHelper.ts} (79%) create mode 100644 packages/main/cypress/specs/Tree.cy.ts rename packages/main/cypress/specs/base/{AriaLabelHelper.cy.ts => AccessibilityTextsHelper.cy.ts} (89%) rename packages/main/test/pages/base/{AriaLabelHelper.html => AccessibilityTextsHelper.html} (99%) diff --git a/docs/2-advanced/09-accessibility.md b/docs/2-advanced/09-accessibility.md index b77b078dc379..59923bb3c2bf 100644 --- a/docs/2-advanced/09-accessibility.md +++ b/docs/2-advanced/09-accessibility.md @@ -141,6 +141,8 @@ The mapping of the accessibility APIs to ARIA attributes is described in the fol | ------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `accessibleName` | `aria-label` | Defines the text alternative of the component. If not provided, a default text alternative is set, if present. | | `accessibleNameRef` | `aria-label` | Alternative for `aria-labelledby`. Receives ID (or many IDs) of the elements that serve as labels of the component. Those labels are passed as a concatenated string to the `aria-label` attribute. | +| `accessibleDescription` | `aria-description` | Defines the description of the component. | +| `accessibleDescriptionRef` | `aria-description` | Alternative for `aria-describedby`. Receives ID (or many IDs) of the elements that serve as descriptions of the component. Those descriptions are passed as a concatenated string to the `aria-describedby` attribute. | | `accessibleRole` | `role` | Sets the accessible aria role of the component. | | `accessibilityAttributes` | `aria-expanded`, `aria-haspopup`, `aria-controls`, etc. | An object of strings that defines several additional accessibility attribute values for customization depending on the use case.
For composite components the object provides a way to enrich the accessibility of the different elements inside the component (for example in the `ui5-shellbar`). | | | `required` | `aria-required` | Defines whether the component is required. | @@ -187,6 +189,53 @@ The `accessibleNameRef` property is currently supported in most of the available --- +### accessibleDescription + +Setting the property on the custom element as: +```html + + Item 1 + Item 2 + +``` + +Will result in the shadow DOM as: +```html + +``` + +The `accessibleDescription` property is currently supported in: +* [List](https://sap.github.io/ui5-webcomponents/nightly/components/List/) +* [Tree](https://sap.github.io/ui5-webcomponents/nightly/components/Tree/) + +--- + +### accessibleDescriptionRef + +Setting the property on the custom element as: +```html +

List of items

+ + Item 1 + Item 2 + +``` + +Will result in the shadow DOM as: +```html + +``` + +The `accessibleDescriptionRef` property is currently supported in: +* [List](https://sap.github.io/ui5-webcomponents/nightly/components/List/) +* [Tree](https://sap.github.io/ui5-webcomponents/nightly/components/Tree/) + +--- + ### accessibleRole Setting the property on the custom element as: diff --git a/packages/base/src/util/AriaLabelHelper.ts b/packages/base/src/util/AccessibilityTextsHelper.ts similarity index 79% rename from packages/base/src/util/AriaLabelHelper.ts rename to packages/base/src/util/AccessibilityTextsHelper.ts index dbf687fa4113..89e11d9e0cba 100644 --- a/packages/base/src/util/AriaLabelHelper.ts +++ b/packages/base/src/util/AccessibilityTextsHelper.ts @@ -20,6 +20,8 @@ const registeredElements = new WeakMap(); type AccessibleElement = HTMLElement & { accessibleNameRef?: string; accessibleName?: string; + accessibleDescriptionRef?: string; + accessibleDescription?: string; }; const observerOptions = { @@ -49,11 +51,10 @@ const getEffectiveAriaLabelText = (el: HTMLElement) => { */ const getAllAccessibleNameRefTexts = (el: HTMLElement) => { const ids = (el as AccessibleElement).accessibleNameRef?.split(" ") ?? []; - const owner = el.getRootNode() as HTMLElement; let result = ""; ids.forEach((elementId: string, index: number) => { - const element = owner.querySelector(`[id='${elementId}']`); + const element = _getReferencedElementById(el, elementId); const text = `${element && element.textContent ? element.textContent : ""}`; if (text) { result += text; @@ -74,8 +75,12 @@ const _getAllAssociatedElementsFromDOM = (el: UI5Element): Array => set.add(itm); }); // adding other elements that id is the same as accessibleNameRef value - const value = el["accessibleNameRef" as keyof typeof el] as string; - const ids = value?.split(" ") ?? []; + const ariaLabelledBy = el["accessibleNameRef" as keyof typeof el] as string; + const ariaDescribedBy = el["accessibleDescriptionRef" as keyof typeof el] as string; + + const value = [ariaLabelledBy, ariaDescribedBy].filter(Boolean).join(" "); + + const ids = value ? value.split(" ") : []; ids.forEach(id => { const refEl = _getReferencedElementById(el, id); if (refEl) { @@ -91,7 +96,7 @@ const _getAssociatedLabels = (el: HTMLElement): Array => { }; const _getReferencedElementById = (el: HTMLElement, elementId: string): HTMLElement | null => { - return (el.getRootNode() as HTMLElement).querySelector(`[id='${elementId}']`); + return (el.getRootNode() as HTMLElement).querySelector(`[id='${elementId}']`) || document.getElementById(elementId); }; /** @@ -115,7 +120,9 @@ const getAssociatedLabelForTexts = (el: HTMLElement) => { const _createInvalidationCallback = (el: UI5Element) => { const invalidationCallback = (changeInfo: ChangeInfo) => { - if (!(changeInfo && changeInfo.type === "property" && changeInfo.name === "accessibleNameRef")) { + const isAccessibleNameRefChange = changeInfo && changeInfo.type === "property" && changeInfo.name === "accessibleNameRef"; + const isAccessibleDescriptionRefChange = changeInfo && changeInfo.type === "property" && changeInfo.name === "accessibleDescriptionRef"; + if (!isAccessibleNameRefChange && !isAccessibleDescriptionRefChange) { return; } const registeredElement = registeredElements.get(el); @@ -210,10 +217,44 @@ const deregisterUI5Element = (el: UI5Element) => { registeredElements.delete(el); }; +const getEffectiveAriaDescriptionText = (el: HTMLElement) => { + const accessibleEl = el as AccessibleElement; + + if (!accessibleEl.accessibleDescriptionRef) { + if (accessibleEl.accessibleDescription) { + return accessibleEl.accessibleDescription; + } + + return undefined; + } + + return getAllAccessibleDescriptionRefTexts(el); +}; + +const getAllAccessibleDescriptionRefTexts = (el: HTMLElement) => { + const ids = (el as AccessibleElement).accessibleDescriptionRef?.split(" ") ?? []; + let result = ""; + + ids.forEach((elementId: string, index: number) => { + const element = _getReferencedElementById(el, elementId); + const text = `${element && element.textContent ? element.textContent : ""}`; + if (text) { + result += text; + if (index < ids.length - 1) { + result += " "; + } + } + }); + + return result; +}; + export { getEffectiveAriaLabelText, getAssociatedLabelForTexts, registerUI5Element, deregisterUI5Element, getAllAccessibleNameRefTexts, + getEffectiveAriaDescriptionText, + getAllAccessibleDescriptionRefTexts, }; diff --git a/packages/compat/src/Table.ts b/packages/compat/src/Table.ts index 163000ca07b8..81f57ef019d2 100644 --- a/packages/compat/src/Table.ts +++ b/packages/compat/src/Table.ts @@ -30,7 +30,7 @@ import { import getNormalizedTarget from "@ui5/webcomponents-base/dist/util/getNormalizedTarget.js"; import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js"; import { getLastTabbableElement, getTabbableElements } from "@ui5/webcomponents-base/dist/util/TabbableElements.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import debounce from "@ui5/webcomponents-base/dist/util/debounce.js"; import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; import CheckBox from "@ui5/webcomponents/dist/CheckBox.js"; diff --git a/packages/fiori/src/IllustratedMessage.ts b/packages/fiori/src/IllustratedMessage.ts index 0e51b6e9c2eb..20a5fa04f6f4 100644 --- a/packages/fiori/src/IllustratedMessage.ts +++ b/packages/fiori/src/IllustratedMessage.ts @@ -6,7 +6,7 @@ import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import { getIllustrationDataSync, getIllustrationData } from "@ui5/webcomponents-base/dist/asset-registries/Illustrations.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import Title from "@ui5/webcomponents/dist/Title.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; diff --git a/packages/main/cypress/specs/Tree.cy.ts b/packages/main/cypress/specs/Tree.cy.ts new file mode 100644 index 000000000000..307d02493595 --- /dev/null +++ b/packages/main/cypress/specs/Tree.cy.ts @@ -0,0 +1,29 @@ +import { html } from "lit"; +import "../../src/Tree.js"; +import "../../src/TreeItem.js"; + +describe("Tree Tests", () => { + it("tests accessibility properties forwarded to the list", () => { + cy.mount(html` + +
Tree
+
Description
+ `); + + cy.get("[ui5-tree]") + .as("tree"); + + cy.get("@tree") + .shadow() + .find(".ui5-tree-root") + .should("have.attr", "accessible-name", "Tree") + .and("have.attr", "accessible-name-ref", "lblDesc1") + .and("have.attr", "accessible-description", "Description") + .and("have.attr", "accessible-description-ref", "lblDesc2"); + }); +}); diff --git a/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts b/packages/main/cypress/specs/base/AccessibilityTextsHelper.cy.ts similarity index 89% rename from packages/main/cypress/specs/base/AriaLabelHelper.cy.ts rename to packages/main/cypress/specs/base/AccessibilityTextsHelper.cy.ts index 2a8bb8c115ed..82a3c1f24c92 100644 --- a/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts +++ b/packages/main/cypress/specs/base/AccessibilityTextsHelper.cy.ts @@ -1,8 +1,9 @@ import { html } from "lit"; import "../../../src/Label.js"; import "../../../src/Input.js"; +import "../../../src/List.js"; -describe("AriaLabelHelper", () => { +describe("AccessibilityTextsHelper", () => { it("Label-for tests", () => { cy.mount(html` @@ -277,4 +278,39 @@ describe("AriaLabelHelper", () => { cy.get("@input") .should("have.attr", "aria-label", "Desc1X Desc4X"); }); + + it("Tests accessibleDescription and accessibleDescriptionRef with ui5-list", () => { + cy.mount(html` + Desc1 + Desc2 + + `); + + cy.get("#list") + .shadow() + .find("ul") + .as("list"); + + // assert + cy.get("@list") + .should("have.attr", "aria-description", "Desc1 Desc2"); + + // act - update text of referenced label + cy.get("#lblDesc1") + .then($el => { + $el.get(0).innerHTML = `${$el.get(0).innerHTML}X`; + }); + + // assert + cy.get("@list") + .should("have.attr", "aria-description", "Desc1X Desc2"); + + // act - update accessible-description-ref + cy.get("#list") + .invoke("removeAttr", "accessible-description-ref"); + + // assert + cy.get("@list") + .should("have.attr", "aria-description", "Desc3"); + }); }); diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index 815026534eb6..2b4c2967103c 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -11,7 +11,7 @@ import { isEscape, isShift, } from "@ui5/webcomponents-base/dist/Keys.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import type { AccessibilityAttributes, PassiveEventListenerObject } from "@ui5/webcomponents-base/dist/types.js"; import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; diff --git a/packages/main/src/Card.ts b/packages/main/src/Card.ts index aa1270ce8e83..72def5679fdd 100644 --- a/packages/main/src/Card.ts +++ b/packages/main/src/Card.ts @@ -5,7 +5,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import CardTemplate from "./generated/templates/CardTemplate.lit.js"; import Icon from "./Icon.js"; import BusyIndicator from "./BusyIndicator.js"; diff --git a/packages/main/src/Carousel.ts b/packages/main/src/Carousel.ts index 29a950355f45..381086899ae4 100644 --- a/packages/main/src/Carousel.ts +++ b/packages/main/src/Carousel.ts @@ -21,7 +21,7 @@ import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js"; import AnimationMode from "@ui5/webcomponents-base/dist/types/AnimationMode.js"; import { getAnimationMode } from "@ui5/webcomponents-base/dist/config/AnimationMode.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import { CAROUSEL_OF_TEXT, CAROUSEL_DOT_TEXT, diff --git a/packages/main/src/CheckBox.ts b/packages/main/src/CheckBox.ts index 614a87df4b62..3aba8b9f523a 100644 --- a/packages/main/src/CheckBox.ts +++ b/packages/main/src/CheckBox.ts @@ -7,7 +7,7 @@ import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js"; import "@ui5/webcomponents-icons/dist/accept.js"; import "@ui5/webcomponents-icons/dist/complete.js"; diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 3501380da787..a2982a5ce1b9 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -7,7 +7,7 @@ import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import { isPhone, isAndroid } from "@ui5/webcomponents-base/dist/Device.js"; import InvisibleMessageMode from "@ui5/webcomponents-base/dist/types/InvisibleMessageMode.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsScope.js"; import "@ui5/webcomponents-icons/dist/slim-arrow-down.js"; diff --git a/packages/main/src/DatePicker.ts b/packages/main/src/DatePicker.ts index 9d651423fd86..38b3bc73706b 100644 --- a/packages/main/src/DatePicker.ts +++ b/packages/main/src/DatePicker.ts @@ -11,7 +11,7 @@ import modifyDateBy from "@ui5/webcomponents-localization/dist/dates/modifyDateB import getRoundedTimestamp from "@ui5/webcomponents-localization/dist/dates/getRoundedTimestamp.js"; import getTodayUTCTimestamp from "@ui5/webcomponents-localization/dist/dates/getTodayUTCTimestamp.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import { submitForm } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js"; import type { IFormInputElement } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js"; import { diff --git a/packages/main/src/Input.ts b/packages/main/src/Input.ts index 2ec93549fd52..0ac3733bcc5d 100644 --- a/packages/main/src/Input.ts +++ b/packages/main/src/Input.ts @@ -40,7 +40,7 @@ import { getAllAccessibleNameRefTexts, registerUI5Element, deregisterUI5Element, -} from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +} from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import { getCaretPosition, setCaretPosition } from "@ui5/webcomponents-base/dist/util/Caret.js"; import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js"; import "@ui5/webcomponents-icons/dist/decline.js"; diff --git a/packages/main/src/Link.ts b/packages/main/src/Link.ts index bc59affd3718..9f362fba991f 100644 --- a/packages/main/src/Link.ts +++ b/packages/main/src/Link.ts @@ -5,7 +5,7 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import type { AccessibilityAttributes } from "@ui5/webcomponents-base/dist/types.js"; import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type { I18nText } from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; diff --git a/packages/main/src/List.hbs b/packages/main/src/List.hbs index 81f530ab158a..3dc96dec46e9 100644 --- a/packages/main/src/List.hbs +++ b/packages/main/src/List.hbs @@ -44,6 +44,7 @@ role="{{listAccessibleRole}}" aria-label="{{ariaLabelTxt}}" aria-labelledby="{{ariaLabelledBy}}" + aria-description="{{ariaDescriptionText}}" > diff --git a/packages/main/src/List.ts b/packages/main/src/List.ts index 8d45a0842e80..ea3577be1ea9 100644 --- a/packages/main/src/List.ts +++ b/packages/main/src/List.ts @@ -20,7 +20,14 @@ import { import DragRegistry from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; import { findClosestPosition, findClosestPositionsByKey } from "@ui5/webcomponents-base/dist/util/dragAndDrop/findClosestPosition.js"; import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { + getAllAccessibleDescriptionRefTexts, + getEffectiveAriaDescriptionText, + getEffectiveAriaLabelText, + registerUI5Element, + deregisterUI5Element, + getAllAccessibleNameRefTexts, +} from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import getNormalizedTarget from "@ui5/webcomponents-base/dist/util/getNormalizedTarget.js"; import getEffectiveScrollbarStyle from "@ui5/webcomponents-base/dist/util/getEffectiveScrollbarStyle.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; @@ -459,7 +466,7 @@ class List extends UI5Element { accessibleName?: string; /** - * Defines the IDs of the elements that label the input. + * Defines the IDs of the elements that label the component. * @default undefined * @public * @since 1.0.0-rc.15 @@ -467,6 +474,38 @@ class List extends UI5Element { @property() accessibleNameRef?: string; + /** + * Defines the accessible description of the component. + * @default undefined + * @public + * @since 2.5.0 + */ + @property() + accessibleDescription?: string; + + /** + * Defines the IDs of the elements that describe the component. + * @default undefined + * @public + * @since 2.5.0 + */ + @property() + accessibleDescriptionRef?: string; + + /** + * Constantly updated value of texts collected from the associated labels + * @private + */ + @property({ noAttribute: true }) + _associatedDescriptionRefTexts?: string; + + /** + * Constantly updated value of texts collected from the associated labels + * @private + */ + @property({ noAttribute: true }) + _associatedLabelsRefTexts?: string; + /** * Defines the accessible role of the component. * @public @@ -576,11 +615,18 @@ class List extends UI5Element { return this.getItems(); } + _updateAssociatedLabelsTexts() { + this._associatedDescriptionRefTexts = getAllAccessibleDescriptionRefTexts(this); + this._associatedLabelsRefTexts = getAllAccessibleNameRefTexts(this); + } + onEnterDOM() { + registerUI5Element(this, this._updateAssociatedLabelsTexts.bind(this)); DragRegistry.subscribe(this); } onExitDOM() { + deregisterUI5Element(this); this.unobserveListEnd(); this.resizeListenerAttached = false; ResizeHandler.deregister(this.getDomRef()!, this._handleResize); @@ -704,7 +750,11 @@ class List extends UI5Element { } get ariaLabelTxt() { - return getEffectiveAriaLabelText(this); + return this._associatedLabelsRefTexts || getEffectiveAriaLabelText(this); + } + + get ariaDescriptionText() { + return this._associatedDescriptionRefTexts || getEffectiveAriaDescriptionText(this); } get ariaLabelModeText(): string { diff --git a/packages/main/src/MultiComboBox.ts b/packages/main/src/MultiComboBox.ts index dc5f0a61e696..6003c8579054 100644 --- a/packages/main/src/MultiComboBox.ts +++ b/packages/main/src/MultiComboBox.ts @@ -51,7 +51,7 @@ import "@ui5/webcomponents-icons/dist/error.js"; import "@ui5/webcomponents-icons/dist/alert.js"; import "@ui5/webcomponents-icons/dist/sys-enter-2.js"; import "@ui5/webcomponents-icons/dist/information.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsScope.js"; import { submitForm } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js"; import type { IFormInputElement } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js"; diff --git a/packages/main/src/Popup.ts b/packages/main/src/Popup.ts index 956610307291..6a916618a86d 100644 --- a/packages/main/src/Popup.ts +++ b/packages/main/src/Popup.ts @@ -12,7 +12,7 @@ import { isPhone, } from "@ui5/webcomponents-base/dist/Device.js"; import { getFirstFocusableElement, getLastFocusableElement } from "@ui5/webcomponents-base/dist/util/FocusableElements.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import { hasStyle, createStyle } from "@ui5/webcomponents-base/dist/ManagedStyles.js"; import { isEnter, isTabPrevious } from "@ui5/webcomponents-base/dist/Keys.js"; import { getFocusedElement, isFocusedElementWithinNode } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; diff --git a/packages/main/src/RadioButton.ts b/packages/main/src/RadioButton.ts index a57f2af8c270..27a1edb06366 100644 --- a/packages/main/src/RadioButton.ts +++ b/packages/main/src/RadioButton.ts @@ -7,7 +7,7 @@ import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import type { IFormInputElement } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js"; import { isSpace, diff --git a/packages/main/src/RatingIndicator.ts b/packages/main/src/RatingIndicator.ts index 5ae268f65059..aa989847ce76 100644 --- a/packages/main/src/RatingIndicator.ts +++ b/packages/main/src/RatingIndicator.ts @@ -14,7 +14,7 @@ import { isHome, isEnd, } from "@ui5/webcomponents-base/dist/Keys.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } 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 { diff --git a/packages/main/src/SegmentedButtonItem.ts b/packages/main/src/SegmentedButtonItem.ts index c6318f0e9575..a516879eaece 100644 --- a/packages/main/src/SegmentedButtonItem.ts +++ b/packages/main/src/SegmentedButtonItem.ts @@ -6,7 +6,7 @@ import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import { getEnableDefaultTooltips } from "@ui5/webcomponents-base/dist/config/Tooltips.js"; import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js"; import { isSpaceShift } from "@ui5/webcomponents-base/dist/Keys.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import willShowContent from "@ui5/webcomponents-base/dist/util/willShowContent.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; diff --git a/packages/main/src/Select.ts b/packages/main/src/Select.ts index 47f64d9942bc..aadec8c3843b 100644 --- a/packages/main/src/Select.ts +++ b/packages/main/src/Select.ts @@ -17,7 +17,7 @@ import { isTabPrevious, } from "@ui5/webcomponents-base/dist/Keys.js"; import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import "@ui5/webcomponents-icons/dist/slim-arrow-down.js"; import "@ui5/webcomponents-icons/dist/error.js"; diff --git a/packages/main/src/StepInput.ts b/packages/main/src/StepInput.ts index 34437c438045..75455c375850 100644 --- a/packages/main/src/StepInput.ts +++ b/packages/main/src/StepInput.ts @@ -20,7 +20,7 @@ import { import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import type { Timeout } from "@ui5/webcomponents-base/dist/types.js"; import type { IFormInputElement } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js"; diff --git a/packages/main/src/Switch.ts b/packages/main/src/Switch.ts index f90929c28144..796faacb0eaf 100644 --- a/packages/main/src/Switch.ts +++ b/packages/main/src/Switch.ts @@ -10,7 +10,7 @@ import { isDesktop, isSafari } from "@ui5/webcomponents-base/dist/Device.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type { ClassMap } from "@ui5/webcomponents-base/dist/types.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import "@ui5/webcomponents-icons/dist/accept.js"; import "@ui5/webcomponents-icons/dist/decline.js"; import "@ui5/webcomponents-icons/dist/less.js"; diff --git a/packages/main/src/Table.ts b/packages/main/src/Table.ts index cb75c5c1f126..4ef5af1314a3 100644 --- a/packages/main/src/Table.ts +++ b/packages/main/src/Table.ts @@ -5,7 +5,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsScope.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; diff --git a/packages/main/src/TextArea.ts b/packages/main/src/TextArea.ts index fe0e9d4224aa..19ba9b3d2b6d 100644 --- a/packages/main/src/TextArea.ts +++ b/packages/main/src/TextArea.ts @@ -7,7 +7,7 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.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/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText, getAssociatedLabelForTexts } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import getEffectiveScrollbarStyle from "@ui5/webcomponents-base/dist/util/getEffectiveScrollbarStyle.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; diff --git a/packages/main/src/TimePicker.ts b/packages/main/src/TimePicker.ts index 25c2b894ee91..8382c78246cc 100644 --- a/packages/main/src/TimePicker.ts +++ b/packages/main/src/TimePicker.ts @@ -11,7 +11,7 @@ import { submitForm } from "@ui5/webcomponents-base/dist/features/InputElementsF import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import "@ui5/webcomponents-localization/dist/features/calendar/Gregorian.js"; // default calendar for bundling import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js"; diff --git a/packages/main/src/Tokenizer.ts b/packages/main/src/Tokenizer.ts index 2acd01251027..8e43b80c47d4 100644 --- a/packages/main/src/Tokenizer.ts +++ b/packages/main/src/Tokenizer.ts @@ -9,7 +9,7 @@ import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.j import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js"; import { getFocusedElement } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; import ScrollEnablement from "@ui5/webcomponents-base/dist/delegate/ScrollEnablement.js"; diff --git a/packages/main/src/Toolbar.ts b/packages/main/src/Toolbar.ts index a68ee1b2677a..1da5caa61b8a 100644 --- a/packages/main/src/Toolbar.ts +++ b/packages/main/src/Toolbar.ts @@ -9,7 +9,7 @@ import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; +import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js"; import "@ui5/webcomponents-icons/dist/overflow.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; diff --git a/packages/main/src/Tree.hbs b/packages/main/src/Tree.hbs index 5493684efae3..6fc08787aa27 100644 --- a/packages/main/src/Tree.hbs +++ b/packages/main/src/Tree.hbs @@ -4,7 +4,10 @@ .footerText="{{footerText}}" .noDataText="{{noDataText}}" .accessibleRole="{{_role}}" - .accessibleName="{{_label}}" + .accessibleName="{{accessibleName}}" + .accessibleNameRef="{{accessibleNameRef}}" + .accessibleDescription="{{accessibleDescription}}" + .accessibleDescriptionRef="{{accessibleDescriptionRef}}" @dragenter="{{_ondragenter}}" @dragover="{{_ondragover}}" @drop="{{_ondrop}}" diff --git a/packages/main/src/Tree.ts b/packages/main/src/Tree.ts index f549e1e4a5d4..8c37a38a4ff6 100644 --- a/packages/main/src/Tree.ts +++ b/packages/main/src/Tree.ts @@ -8,7 +8,6 @@ import Orientation from "@ui5/webcomponents-base/dist/types/Orientation.js"; import MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; -import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; import DropIndicator from "./DropIndicator.js"; import TreeItem from "./TreeItem.js"; import type TreeItemBase from "./TreeItemBase.js"; @@ -304,6 +303,24 @@ class Tree extends UI5Element { @property() accessibleNameRef?: string; + /** + * Defines the accessible description of the component. + * @default undefined + * @public + * @since 2.5.0 + */ + @property() + accessibleDescription?: string; + + /** + * Defines the IDs of the elements that describe the component. + * @default undefined + * @public + * @since 2.5.0 + */ + @property() + accessibleDescriptionRef?: string; + /** * Defines the items of the component. Tree items may have other tree items as children. * @@ -353,10 +370,6 @@ class Tree extends UI5Element { return ListAccessibleRole.Tree; } - get _label() { - return getEffectiveAriaLabelText(this); - } - get _hasHeader() { return !!this.header.length; } diff --git a/packages/main/test/pages/List.html b/packages/main/test/pages/List.html index 339f1499df55..4735dc05c240 100644 --- a/packages/main/test/pages/List.html +++ b/packages/main/test/pages/List.html @@ -497,6 +497,23 @@

Items 3/3

+

+

With accessible description ref

+ Accessible description ref works + + Item 1 + Item 2 + Item 3 + + +

+

With accessible description

+ + Item 1 + Item 2 + Item 3 + +
diff --git a/packages/main/test/pages/Tree.html b/packages/main/test/pages/Tree.html index afc5d5b22123..eeb2899ee481 100644 --- a/packages/main/test/pages/Tree.html +++ b/packages/main/test/pages/Tree.html @@ -206,6 +206,25 @@ + +

+ +

With accessible description ref

+ Tree with accessible description ref + + + + + + +

+

With accessible description

+ + + + + +