From 2c59849072ee1004ddca9ca004deaf8d09be930c Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 1 Apr 2026 02:17:05 +0200 Subject: [PATCH] [v15.0/forgejo] Fix @mention combobox semantics for screen reader accessibility (#11922) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/11860 Fixes https://codeberg.org/forgejo/forgejo/issues/7668. This was simpler to fix than my theory I posted on https://codeberg.org/forgejo/forgejo/issues/7668 about needing to patch the upstream package. When testing in Firefox with the developer console open and warnings enabled, I noticed a `Empty string passed to getElementById()` warning coming from `@github/combobox-nav` while attempting to manage the `aria-activedescendant` attribute. Then I found this in the [README for that project](https://github.com/github/combobox-nav). > Markup requirements: > - Each option needs to have role="option" and a unique id This was easy to miss, as we're using `@github/text-expander-element` and the combobox-nav package is one of _its_ dependencies. Without a unique ID on each dropdown menu item, `@github/text-expander-element` is unable to set an appropriate `aria-activedescendant` attribute on the textarea. Once that's in place, the screen reader announcements come to life beautifully. While working on it I noticed the emoji picker combobox was affected by the same problem and patched that as well. Co-authored-by: Henry Catalini Smith Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11922 Reviewed-by: Otto Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- tests/e2e/issue-comment.test.e2e.ts | 2 +- web_src/js/features/comp/TextExpander.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e/issue-comment.test.e2e.ts b/tests/e2e/issue-comment.test.e2e.ts index 94e4bb7243..ed1c79a585 100644 --- a/tests/e2e/issue-comment.test.e2e.ts +++ b/tests/e2e/issue-comment.test.e2e.ts @@ -331,7 +331,7 @@ test('Emoji suggestions', async ({page}) => { ]; for (const {emoji, name} of expectedSuggestions) { - const item = suggestionList.locator(`li:has-text("${name}")`); + const item = suggestionList.locator(`[id="combobox-emoji-${name}"]`); await expect(item).toContainText(`${emoji} ${name}`); } diff --git a/web_src/js/features/comp/TextExpander.js b/web_src/js/features/comp/TextExpander.js index 8777f3a334..ae652a013b 100644 --- a/web_src/js/features/comp/TextExpander.js +++ b/web_src/js/features/comp/TextExpander.js @@ -12,6 +12,7 @@ export function initTextExpander(expander) { ul.classList.add('suggestions'); for (const name of matches) { const li = document.createElement('li'); + li.setAttribute('id', `combobox-emoji-${name}`); li.setAttribute('role', 'option'); li.setAttribute('data-value', emojiString(name)); if (customEmojis.has(name)) { @@ -33,10 +34,12 @@ export function initTextExpander(expander) { ul.classList.add('suggestions'); for (const {value, name, fullname, avatar} of matches) { const li = document.createElement('li'); + li.setAttribute('id', `combobox-user-${name}`); li.setAttribute('role', 'option'); li.setAttribute('data-value', `${key}${value}`); const img = document.createElement('img'); + img.setAttribute('aria-hidden', 'true'); img.src = avatar; li.append(img);