Skip to content

Commit a91aa93

Browse files
authoredNov 19, 2024··
[SelectPanel] Fix result count in screen reader announcements (#3187)
1 parent 856a641 commit a91aa93

File tree

9 files changed

+105
-58
lines changed

9 files changed

+105
-58
lines changed
 

‎.changeset/wise-donuts-cheer.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/view-components': patch
3+
---
4+
5+
[SelectPanel] Fix result count in screen reader announcements

‎app/components/primer/alpha/action_list/item.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def before_render
302302
if @list.allows_selection?
303303
@content_arguments[:aria] = merge_aria(
304304
@content_arguments,
305-
{ aria: @list.aria_selection_variant == :selected ? {selected: active?} : { checked: active? } }
305+
{ aria: @list.aria_selection_variant == :selected ? { selected: active? } : { checked: active? } }
306306
)
307307
end
308308

‎app/components/primer/alpha/select_panel.html.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
<% end %>
4848
<%= render Primer::Alpha::Dialog::Body.new(mt: show_filter? ? 0 : 2, p: 0) do %>
4949
<focus-group direction="vertical" mnemonics retain>
50-
<div class="sr-only" aria-live="polite" aria-atomic="true" data-target="select-panel.ariaLiveContainer"></div>
50+
<live-region data-target="select-panel.liveRegion"></live-region>
5151
<%= render(Primer::BaseComponent.new(
5252
tag: :div,
5353
data: {

‎app/components/primer/alpha/select_panel_element.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {getAnchoredPosition} from '@primer/behaviors'
22
import {controller, target} from '@github/catalyst'
3-
import {announceFromElement, announce} from '../aria_live'
43
import {IncludeFragmentElement} from '@github/include-fragment-element'
54
import type {PrimerTextFieldElement} from 'app/lib/primer/forms/primer_text_field'
65
import type {AnchorAlignment, AnchorSide} from '@primer/behaviors'
6+
import type {LiveRegionElement} from '@primer/live-region-element'
7+
import '@primer/live-region-element'
78
import '@oddbird/popover-polyfill'
89

910
type SelectVariant = 'none' | 'single' | 'multiple' | null
@@ -69,11 +70,11 @@ export class SelectPanelElement extends HTMLElement {
6970
@target filterInputTextField: HTMLInputElement
7071
@target remoteInput: HTMLElement
7172
@target list: HTMLElement
72-
@target ariaLiveContainer: HTMLElement
7373
@target noResults: HTMLElement
7474
@target fragmentErrorElement: HTMLElement
7575
@target bannerErrorElement: HTMLElement
7676
@target bodySpinner: HTMLElement
77+
@target liveRegion: LiveRegionElement
7778

7879
filterFn?: FilterFn
7980

@@ -412,7 +413,7 @@ export class SelectPanelElement extends HTMLElement {
412413
if (this.#loadingAnnouncementTimeoutId) clearTimeout(this.#loadingAnnouncementTimeoutId)
413414

414415
this.#loadingAnnouncementTimeoutId = setTimeout(() => {
415-
announce('Loading', {element: this.ariaLiveContainer})
416+
this.liveRegion.announce('Loading')
416417
}, 2000) as unknown as number
417418

418419
this.#loadingDelayTimeoutId = setTimeout(() => {
@@ -564,7 +565,7 @@ export class SelectPanelElement extends HTMLElement {
564565
const errorElement = this.fragmentErrorElement
565566
// check if the errorElement is visible in the dom
566567
if (errorElement && !errorElement.hasAttribute('hidden')) {
567-
announceFromElement(errorElement, {element: this.ariaLiveContainer, assertive: true})
568+
this.liveRegion.announceFromElement(errorElement, {politeness: 'assertive'})
568569
return
569570
}
570571

@@ -766,7 +767,7 @@ export class SelectPanelElement extends HTMLElement {
766767

767768
// check if the errorElement is visible in the dom
768769
if (errorElement && !errorElement.hasAttribute('hidden')) {
769-
announceFromElement(errorElement, {element: this.ariaLiveContainer, assertive: true})
770+
this.liveRegion.announceFromElement(errorElement, {politeness: 'assertive'})
770771
return
771772
}
772773
}
@@ -778,17 +779,15 @@ export class SelectPanelElement extends HTMLElement {
778779

779780
#maybeAnnounce() {
780781
if (this.open && this.list) {
781-
const items = this.items
782+
const items = this.visibleItems
782783

783784
if (items.length > 0) {
784785
const instructions = 'tab for results'
785-
announce(`${items.length} result${items.length === 1 ? '' : 's'} ${instructions}`, {
786-
element: this.ariaLiveContainer,
787-
})
786+
this.liveRegion.announce(`${items.length} result${items.length === 1 ? '' : 's'} ${instructions}`)
788787
} else {
789788
const noResultsEl = this.noResults
790789
if (noResultsEl) {
791-
announceFromElement(noResultsEl, {element: this.ariaLiveContainer})
790+
this.liveRegion.announceFromElement(noResultsEl)
792791
}
793792
}
794793
}

‎app/components/primer/aria_live.ts

-41
This file was deleted.

‎app/components/primer/primer.ts

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import './anchored_position'
77
import './dialog_helper'
88
import './focus_group'
99
import './scrollable_region'
10-
import './aria_live'
1110
import './shared_events'
1211
import './alpha/modal_dialog'
1312
import './beta/nav_list'

‎package-lock.json

+28-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@github/relative-time-element": "^4.0.0",
5454
"@github/remote-input-element": "^0.4.0",
5555
"@github/tab-container-element": "^3.1.2",
56+
"@primer/live-region-element": "^0.7.1",
5657
"@oddbird/popover-polyfill": "^0.4.0",
5758
"@primer/behaviors": "^1.3.4"
5859
},

‎test/system/alpha/select_panel_test.rb

+60-3
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,24 @@ def filter_results(query:)
100100
find("input").fill_in(with: query)
101101
end
102102

103-
def assert_announces(message:)
103+
def assert_announces(message:, politeness: :polite)
104104
yield
105105

106-
assert_selector "[data-target='select-panel.ariaLiveContainer']" do |element|
107-
assert_includes element.text, message
106+
attempts = 0
107+
max_attempts = 3
108+
109+
begin
110+
attempts += 1
111+
112+
region_text = page.evaluate_script(%Q|
113+
document.querySelector("[data-target='select-panel.liveRegion']")?.shadowRoot?.querySelector('##{politeness}')?.textContent ?? ""
114+
|)
115+
116+
assert_includes region_text, message
117+
rescue Minitest::Assertion => e
118+
raise e if attempts >= max_attempts
119+
sleep 1
120+
retry
108121
end
109122
end
110123

@@ -1281,5 +1294,49 @@ def test_remote_fetch_announces_no_results
12811294
end
12821295
end
12831296
end
1297+
1298+
def test_announces_correct_result_count
1299+
visit_preview(:local_fetch)
1300+
1301+
assert_announces(message: "4 results") do
1302+
click_on_invoker_button
1303+
end
1304+
end
1305+
1306+
def test_announces_correct_result_count_after_filtering
1307+
visit_preview(:local_fetch)
1308+
1309+
click_on_invoker_button
1310+
1311+
assert_announces(message: "1 result") do
1312+
filter_results(query: "2")
1313+
end
1314+
end
1315+
1316+
def test_announces_error_on_initial_failure
1317+
visit_preview(:remote_fetch_initial_failure)
1318+
1319+
assert_announces(message: "Sorry, something went wrong", politeness: :assertive) do
1320+
wait_for_items_to_load do
1321+
click_on_invoker_button
1322+
end
1323+
end
1324+
end
1325+
1326+
def test_announces_error_on_filter_failure
1327+
visit_preview(:remote_fetch_filter_failure)
1328+
1329+
wait_for_items_to_load do
1330+
click_on_invoker_button
1331+
end
1332+
1333+
assert_selector "select-panel ul li"
1334+
1335+
assert_announces(message: "Sorry, something went wrong", politeness: :assertive) do
1336+
wait_for_items_to_load do
1337+
filter_results(query: "foobar")
1338+
end
1339+
end
1340+
end
12841341
end
12851342
end

0 commit comments

Comments
 (0)
Please sign in to comment.