1 /**
2 * This is an implementation of the
3 * aria-live guide:
4 * https://github.com/github/thehub/blob/main/docs/epd/engineering/dev-practicals/frontend/accessibility/readiness-routine/screenreaders/live-regions-and-screen-reader-announcements.md
5 *
6 * The code is heavily borrowed from https://github.com/github/github/blob/master/app/assets/modules/github/aria-live.ts
7 */
8
9 let container
10
11 if (typeof window !== 'undefined') {
12 document.addEventListener('DOMContentLoaded', function () {
13 createNoticeContainer()
14 })
15 }
16
17 // Announce message update to screen reader.
18 export function announce(message) {
19 if (!container || !container.isConnected) {
20 /* This condition is for when the aria-live container no longer exists due to nav methods like turbo drive
21 which replace the body getting rid of the region. We add the container if it's missing. We then add a delay
22 to ensure aria-live can work correctly.
23 Note: This approach is not ideal and should be revisited.
24 See https://github.com/github/accessibility/issues/1900#issuecomment-1254369320
25 */
26 createNoticeContainer()
27 setTimeout(() => {
28 setContainerContent(message)
29 }, 200)
30 } else {
31 setContainerContent(message)
32 }
33 }
34
35 // Set aria-live container to message.
36 function setContainerContent(message) {
37 if (!container) {
38 return
39 }
40 if (container.textContent === message) {
41 /* This is a hack due to the way the aria live API works.
42 A screen reader will not read a live region again
43 if the text is the same. Adding a space character tells
44 the browser that the live region has updated,
45 which will cause it to read again, but with no audible difference. */
46 container.textContent = `${message}\u00A0`
47 } else {
48 container.textContent = message
49 }
50 }
51
52 // Get the global screen reader notice container.
53 function createNoticeContainer() {
54 container = document.createElement('div')
55 container.setAttribute('aria-live', 'polite')
56 container.style.position = 'absolute'
57 container.style.width = '1px'
58 container.style.height = '1px'
59 container.style.padding = '0'
60 container.style.overflow = 'hidden'
61 container.style.clip = 'rect(0, 0, 0, 0)'
62 container.style.wordWrap = 'normal'
63 container.style.border = '0'
64 document.body.append(container)
65 }