From f0f887bcb5695e1541ffef4909f7d7396bfa3545 Mon Sep 17 00:00:00 2001 From: Elena Stoyanova Date: Thu, 8 Aug 2024 14:13:30 +0300 Subject: [PATCH] fix(ui5-combobox, ui5-multi-combobox): accessibility improvements --- packages/fiori/src/WizardTab.hbs | 2 +- packages/main/src/ComboBox.hbs | 1 + packages/main/src/ComboBox.ts | 4 +++ packages/main/src/ComboBoxPopover.hbs | 2 +- packages/main/src/MultiComboBox.hbs | 1 + packages/main/src/MultiComboBox.ts | 4 +++ packages/main/src/MultiComboBoxPopover.hbs | 1 + packages/main/src/MultiInput.hbs | 1 + packages/main/src/MultiInput.ts | 6 +++- packages/main/src/RangeSlider.hbs | 4 +-- packages/main/src/Slider.hbs | 2 +- .../main/src/i18n/messagebundle.properties | 3 ++ packages/main/test/specs/ComboBox.spec.js | 21 ++++++++++++ .../main/test/specs/MultiComboBox.spec.js | 33 ++++++++++++++----- 14 files changed, 71 insertions(+), 14 deletions(-) diff --git a/packages/fiori/src/WizardTab.hbs b/packages/fiori/src/WizardTab.hbs index 19e182e8aab7..3d62ffaaabaf 100644 --- a/packages/fiori/src/WizardTab.hbs +++ b/packages/fiori/src/WizardTab.hbs @@ -13,7 +13,7 @@
{{#if icon}} - + {{else}} {{number}} {{/if}} diff --git a/packages/main/src/ComboBox.hbs b/packages/main/src/ComboBox.hbs index f1b695ef7656..f2da829028ec 100644 --- a/packages/main/src/ComboBox.hbs +++ b/packages/main/src/ComboBox.hbs @@ -24,6 +24,7 @@ aria-describedby="value-state-description" aria-label="{{ariaLabelText}}" aria-required="{{required}}" + aria-controls="{{responsivePopoverId}}" autocomplete="off" data-sap-focus-ref /> diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 723cc9d38a91..c1697dedd52e 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -1237,6 +1237,10 @@ class ComboBox extends UI5Element { return ComboBox.i18nBundle.getText(INPUT_CLEAR_ICON_ACC_NAME); } + get responsivePopoverId() { + return `${this._id}-popover`; + } + static async onDefine() { ComboBox.i18nBundle = await getI18nBundle("@ui5/webcomponents"); } diff --git a/packages/main/src/ComboBoxPopover.hbs b/packages/main/src/ComboBoxPopover.hbs index f099e3a19343..575ceb01a46d 100644 --- a/packages/main/src/ComboBoxPopover.hbs +++ b/packages/main/src/ComboBoxPopover.hbs @@ -44,7 +44,7 @@
- + {{#if hasValueStateText}}
diff --git a/packages/main/src/MultiComboBox.hbs b/packages/main/src/MultiComboBox.hbs index 7b7e5408d36b..358aaa7278ec 100644 --- a/packages/main/src/MultiComboBox.hbs +++ b/packages/main/src/MultiComboBox.hbs @@ -58,6 +58,7 @@ aria-describedby="{{ariaDescribedByText}}" aria-required="{{required}}" aria-label="{{ariaLabelText}}" + aria-controls="{{responsivePopoverId}}" autocomplete="off" data-sap-focus-ref /> diff --git a/packages/main/src/MultiComboBox.ts b/packages/main/src/MultiComboBox.ts index 69c1f394b7cb..73a0d61dca44 100644 --- a/packages/main/src/MultiComboBox.ts +++ b/packages/main/src/MultiComboBox.ts @@ -1921,6 +1921,10 @@ class MultiComboBox extends UI5Element { return MultiComboBox.i18nBundle.getText(MCB_SELECTED_ITEMS, selected.length, items.length); } + get responsivePopoverId() { + return `${this._id}-popover`; + } + get classes(): ClassMap { return { popover: { diff --git a/packages/main/src/MultiComboBoxPopover.hbs b/packages/main/src/MultiComboBoxPopover.hbs index 79ccc237e89c..3dfb4d7c632c 100644 --- a/packages/main/src/MultiComboBoxPopover.hbs +++ b/packages/main/src/MultiComboBoxPopover.hbs @@ -2,6 +2,7 @@ placement-type="Bottom" horizontal-align="Left" class="{{classes.popover}}" + id="{{responsivePopoverId}}" hide-arrow _disable-initial-focus style="{{styles.suggestionsPopover}}" diff --git a/packages/main/src/MultiInput.hbs b/packages/main/src/MultiInput.hbs index 6680528faffa..2f42b265a3d7 100644 --- a/packages/main/src/MultiInput.hbs +++ b/packages/main/src/MultiInput.hbs @@ -36,6 +36,7 @@ @mouseup={{valueHelpMouseUp}} input-icon name="value-help" + accessible-name="{{valueHelpLabel}}" > {{/if}} {{/inline}} diff --git a/packages/main/src/MultiInput.ts b/packages/main/src/MultiInput.ts index a31c172360f6..d03dbbf33d9e 100644 --- a/packages/main/src/MultiInput.ts +++ b/packages/main/src/MultiInput.ts @@ -14,7 +14,7 @@ import { } from "@ui5/webcomponents-base/dist/Keys.js"; import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsScope.js"; -import { MULTIINPUT_ROLEDESCRIPTION_TEXT } from "./generated/i18n/i18n-defaults.js"; +import { MULTIINPUT_ROLEDESCRIPTION_TEXT, MULTIINPUT_VALUE_HELP_LABEL } from "./generated/i18n/i18n-defaults.js"; import Input from "./Input.js"; import MultiInputTemplate from "./generated/templates/MultiInputTemplate.lit.js"; import styles from "./generated/themes/MultiInput.css.js"; @@ -412,6 +412,10 @@ class MultiInput extends Input { }; } + get valueHelpLabel() { + return MultiInput.i18nBundle.getText(MULTIINPUT_VALUE_HELP_LABEL); + } + get ariaRoleDescription() { return MultiInput.i18nBundle.getText(MULTIINPUT_ROLEDESCRIPTION_TEXT); } diff --git a/packages/main/src/RangeSlider.hbs b/packages/main/src/RangeSlider.hbs index 9e16c1637fe2..867637360b0a 100644 --- a/packages/main/src/RangeSlider.hbs +++ b/packages/main/src/RangeSlider.hbs @@ -43,7 +43,7 @@ aria-labelledby="{{_ariaLabelledByStartHandleRefs}}" aria-disabled="{{_ariaDisabled}}" > - + {{#if showTooltip}}
@@ -66,7 +66,7 @@ aria-labelledby="{{_ariaLabelledByEndHandleRefs}}" aria-disabled="{{_ariaDisabled}}" > - + {{#if showTooltip}}
diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index 999cc08cffd5..62703be1540d 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -32,7 +32,7 @@ data-sap-focus-ref part="handle" > - + {{#if showTooltip}}
{{tooltipValue}} diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 1fc299ea78f7..642e12f85f92 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -277,6 +277,9 @@ MULTIINPUT_ROLEDESCRIPTION_TEXT=Multi Value Input #XFLD: Token number indicator which is used to show more tokens in Tokenizers inside MultiInput and MultiComboBox MULTIINPUT_SHOW_MORE_TOKENS={0} More +#XACT: ARIA announcement for value help +MULTIINPUT_VALUE_HELP_LABEL=Show Value Help + #XTOL: Tooltip for panel expand title PANEL_ICON=Expand/Collapse diff --git a/packages/main/test/specs/ComboBox.spec.js b/packages/main/test/specs/ComboBox.spec.js index aeade7899aa3..4b532db15e02 100644 --- a/packages/main/test/specs/ComboBox.spec.js +++ b/packages/main/test/specs/ComboBox.spec.js @@ -884,6 +884,27 @@ describe("Accessibility", async () => { assert.strictEqual(ariaHiddenText.includes("Value State"), true, "Hidden screen reader text is correct"); assert.strictEqual(valueStateText.includes("Custom error"), true, "Displayed value state message text is correct"); }); + + it("Should render aria-haspopup attribute with value 'dialog'", async () => { + await browser.url(`test/pages/ComboBox.html`); + + const combo = await browser.$("#combo"); + const innerInput = await combo.shadow$("input"); + + assert.strictEqual(await innerInput.getAttribute("aria-haspopup"), "dialog", "Should render aria-haspopup attribute with value 'dialog'"); + }); + + it("Should apply aria-controls pointing to the responsive popover", async () => { + await browser.url(`test/pages/ComboBox.html`); + + const combo = await browser.$("#combo"); + const innerInput = await combo.shadow$("input"); + const popover = await combo.shadow$("ui5-responsive-popover"); + + await combo.scrollIntoView(); + + assert.strictEqual(await innerInput.getAttribute("aria-controls"), await popover.getAttribute("id"), "aria-controls attribute is correct."); + }); }); describe("Keyboard navigation", async () => { diff --git a/packages/main/test/specs/MultiComboBox.spec.js b/packages/main/test/specs/MultiComboBox.spec.js index 6af8c241f603..7e160d3006a1 100644 --- a/packages/main/test/specs/MultiComboBox.spec.js +++ b/packages/main/test/specs/MultiComboBox.spec.js @@ -695,7 +695,7 @@ describe("MultiComboBox general interaction", () => { const tokenizerNMore = await cb.shadow$("[ui5-tokenizer]"); const nMoreLabel = await tokenizerNMore.shadow$(".ui5-tokenizer-more-text"); - + await nMoreLabel.click(); assert.ok(await popover.$(".ui5-mcb-select-all-checkbox").getProperty("checked"), "Select All CheckBox should be selected"); @@ -1757,24 +1757,24 @@ describe("MultiComboBox general interaction", () => { it ("Should check clear icon availability", async () => { await browser.url(`test/pages/MultiComboBox.html`); - + const cb = await $("#clear-icon-cb"); const inner = cb.shadow$("input"); const clearIcon = await cb.shadow$(".ui5-input-clear-icon-wrapper"); - + assert.notOk(await cb.getProperty("_effectiveShowClearIcon"), "_effectiveShowClearIcon should be set to false when mcb has no value"); await inner.click(); await inner.keys("c"); - + assert.ok(await cb.getProperty("_effectiveShowClearIcon"), "_effectiveShowClearIcon should be set to true upon typing"); }); - + it ("Should check clear icon events", async () => { await browser.url(`test/pages/MultiComboBox.html`); - + const cb = await $("#clear-icon-cb"); - + await cb.shadow$("input").click(); await cb.shadow$("input").keys("c"); @@ -1782,7 +1782,7 @@ describe("MultiComboBox general interaction", () => { // focus out the combo await clearIcon.click(); - + assert.strictEqual(await $("#clear-icon-change-count").getText(), "0", "change event is not fired"); assert.strictEqual(await $("#clear-icon-input-count").getText(), "2", "input event is fired twice"); }); @@ -1928,6 +1928,23 @@ describe("MultiComboBox general interaction", () => { assert.strictEqual(await innerInput.getAttribute("aria-label"), await mcbLabel.getHTML(false), "aria-label attribute is correct."); }); + it("Should apply aria-controls pointing to the responsive popover", async () => { + const mcb = await browser.$("#mcb-predefined-value"); + const innerInput = await mcb.shadow$("input"); + const popover = await mcb.shadow$("ui5-responsive-popover"); + + await mcb.scrollIntoView(); + + assert.strictEqual(await innerInput.getAttribute("aria-controls"), await popover.getAttribute("id"), "aria-controls attribute is correct."); + }); + + it("Should render aria-haspopup attribute with value 'dialog'", async () => { + const mcb = await browser.$("#mcb-compact"); + const innerInput = await mcb.shadow$("input"); + + assert.strictEqual(await innerInput.getAttribute("aria-haspopup"), "dialog", "Should render aria-haspopup attribute with value 'dialog'"); + }); + it("Value state type should be added to the screen readers default value states announcement", async () => { await browser.url(`test/pages/MultiComboBox.html`);