fix(ui5-select): fix acc finding#13559
Conversation
|
🚀 Deployed on https://pr-13559--ui5-webcomponents-preview.netlify.app |
dobrinyonkov
left a comment
There was a problem hiding this comment.
Thanks for picking this up — the listbox naming fix is a real improvement. Four inline comments below; (1) and (4) would be good to address before merging, (2) and (3) are polish/follow-up.
| onClose={this._afterClose} | ||
| onKeyDown={this._onkeydown} | ||
| accessibleName={this._isPhone ? this._headerTitleText : undefined} | ||
| accessibleName={this._isPhone ? (this.ariaLabelText || this._headerTitleText) : undefined} |
There was a problem hiding this comment.
Spec mismatch — missing Select: prefix (medium).
The spec quote in the PR description literally calls for "Select: + [Name of field]". This sets the mobile dialog's accessibleName to just the field name (e.g. "Country"), not "Select: Country". The dialog role already announces "dialog", so the textual Select: prefix is what distinguishes this dialog from arbitrary other dialogs in the flow — without it, screen-reader users hear "Country, dialog" and lose the cue that this is the Select's value picker.
Could you confirm with the accessibility owner whether the prefix is required? If yes, please introduce a dedicated i18n key (don't hard-code "Select: ") and use it here, roughly:
accessibleName={this._isPhone ? this._effectivePopoverAccessibleName : undefined}where the getter applies the localized prefix to this.ariaLabelText || this._headerTitleText.
| onMouseDown={this._itemMousedown} | ||
| onItemClick={this._handleItemPress} | ||
| accessibleRole="ListBox" | ||
| accessibleName={this.ariaLabelText || this._headerTitleText} |
There was a problem hiding this comment.
Duplicated expression (low).
this.ariaLabelText || this._headerTitleText now appears on both line 30 and line 71. If the precedence ever changes (e.g. a third source is added, or the Select: prefix from comment above lands), it's two places to keep in sync. Consider extracting a getter in Select.ts —
get _effectivePopoverAccessibleName() {
return this.ariaLabelText || this._headerTitleText;
}and using it on both lines. Matches how _headerTitleText is already factored out. Optional but tidy.
| onMouseDown={this._itemMousedown} | ||
| onItemClick={this._handleItemPress} | ||
| accessibleRole="ListBox" | ||
| accessibleName={this.ariaLabelText || this._headerTitleText} |
There was a problem hiding this comment.
Borrowed i18n key — please add a dedicated one (low).
_headerTitleText resolves to INPUT_SUGGESTIONS_TITLE ("All Items"), which was authored for the Input suggestions popover. Now that every Select listbox falls back to this string when the consumer hasn't set a label, it's being used as a Select-specific accessible name even though the key name says otherwise.
Please introduce a Select-specific i18n key (e.g. SELECT_LISTBOX_LABEL / SELECT_POPOVER_TITLE) and have _headerTitleText (or a new getter) resolve to it — even if the English string happens to match. Keeps translations correctly scoped per component and avoids surprises if Input's wording ever diverges.
| onMouseDown={this._itemMousedown} | ||
| onItemClick={this._handleItemPress} | ||
| accessibleRole="ListBox" | ||
| accessibleName={this.ariaLabelText || this._headerTitleText} |
There was a problem hiding this comment.
No test coverage added (medium).
This PR changes ARIA wiring on two surfaces but adds no test. The branch history (two fix(...): fixed failing test commits) suggests this area is easy to regress silently. Two natural assertions belong in the existing accessibility spec at packages/main/cypress/specs/Select.cy.tsx:250:
- Open the popover on a
<Select accessibleName="Foo">→ the inner[ui5-list]hasaria-label="Foo". Add a second case foraccessibleNameRefand a third for a Select with no consumer label (asserting the fallback string). - Simulate
_isPhone→ the[ui5-responsive-popover]hasaria-labelmatching the field's accessible name.
Without these, the next refactor of SelectPopoverTemplate will not catch a regression here.
dobrinyonkov
left a comment
There was a problem hiding this comment.
Thanks for the iterations — all four original comments are addressed: the Select: prefix is in, the dedicated i18n keys exist, the duplication is factored into _effectivePopoverAccessibleName, and there are tests for both surfaces. Three new things worth fixing before merge, plus one nit.
| && value !== null | ||
| && "key" in value | ||
| && "defaultText" in value; | ||
| }; |
There was a problem hiding this comment.
The isI18nText type guard (and the two narrowed branches at lines 1058 and 1142) should be removed — it's working around a stale generated file, not a real type problem.
Generated i18n constants are always typed as I18nText (see packages/main/src/generated/i18n/i18n-defaults.ts — every key is declared const FOO: I18nText = {key, defaultText}). Sibling code in this same file just does:
return Select.i18nBundle.getText(SELECT_ROLE_DESCRIPTION);
return Select.i18nBundle.getText(FORM_SELECTABLE_REQUIRED);no guards needed. The TS error that prompted the workaround was almost certainly because yarn generate hadn't been run after the new keys were added to messagebundle.properties, so the imports in the generated file didn't yet exist. Running yarn generate regenerates the file and the simple form compiles fine.
Leaving the guard in is bad on two counts:
- It's dead code (the branches are unreachable in a correctly generated build) — readers will wonder what edge case it covers.
- The
return ""fallback at line 1062 would silently produce a Select with no listbox label and no mobile dialog label if it ever did fire — masking exactly the regression this PR is supposed to prevent.
Please remove isI18nText, the I18nText named import, and inline the getText calls in both _headerTitleText and _effectivePopoverAccessibleName.
| onMouseDown={this._itemMousedown} | ||
| onItemClick={this._handleItemPress} | ||
| accessibleRole="ListBox" | ||
| accessibleName={this.ariaLabelText || this._headerTitleText} |
There was a problem hiding this comment.
Nit: since _effectivePopoverAccessibleName exists for the popover line, the listbox line could be factored the same way — a small getter _effectiveListAccessibleName returning this.ariaLabelText || this._headerTitleText. Not critical (the two expressions now correctly differ — the popover one carries the Select: prefix), but it keeps the template uniform. Skip if you'd rather not.
| .should("have.attr", "accessible-name") | ||
| .and("equal", _effectivePopoverAccessibleName); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
This assertion is now circular. It reads the value of _effectivePopoverAccessibleName from the element and then asserts the rendered attribute equals that same value — so if the getter is broken, both sides of the equality break the same way and the test still passes.
The other new test (Select.cy.tsx:325) hard-codes "Select: Countries" for the mobile case, so coverage isn't lost — but consider either deleting this case or changing it to assert a literal string. Either way, the property-readback pattern hides regressions.
| SELECT_OPTIONS=Select Options | ||
|
|
||
| #XACT: ARIA announcement prefix for Select dialog accessible name on mobile (without trailing space) | ||
| SELECT_POPOVER_ACCESSIBLE_NAME_PREFIX=Select: |
There was a problem hiding this comment.
Cleaner than embedding the space in the translation — translators can't accidentally drop trailing whitespace, and the join is done explicitly in code. Good fix. (No action needed — leaving the comment here so the rationale is visible in review.)
8) Select (6 failing)