Last active
February 29, 2020 16:02
-
-
Save tryggvigy/ac4473d008756afa32113d1074ea18f1 to your computer and use it in GitHub Desktop.
On demand live region
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { OnDemandLiveRegion } from './OnDemandLiveRegion'; | |
describe('OnDemandLiveRegion', () => { | |
beforeEach(() => { | |
document.body.innerHTML = ''; | |
jest.useFakeTimers(); | |
}); | |
test('works with defaults', () => { | |
const liveRegion = new OnDemandLiveRegion(); | |
liveRegion.say('hi!'); | |
const liveRegionNode = document.querySelector( | |
`[id^="live-region-"]`, | |
) as Element; | |
expect(liveRegionNode.getAttribute('aria-live')).toBe('polite'); | |
expect(liveRegionNode.getAttribute('role')).toBe('status'); | |
jest.advanceTimersByTime(0); | |
expect(liveRegionNode.textContent).toBe('hi!'); | |
}); | |
test('supports assertive announcements', () => { | |
const liveRegion = new OnDemandLiveRegion({ level: 'assertive' }); | |
liveRegion.say('something bad!'); | |
const liveRegionNode = document.querySelector( | |
`[id^="live-region-"]`, | |
) as Element; | |
expect(liveRegionNode.getAttribute('aria-live')).toBe('assertive'); | |
expect(liveRegionNode.getAttribute('role')).toBe('alert'); | |
jest.advanceTimersByTime(0); | |
expect(liveRegionNode.textContent).toBe('something bad!'); | |
}); | |
test('supports delayed announcements', () => { | |
const liveRegion = new OnDemandLiveRegion({ delay: 500 }); | |
liveRegion.say('delayed message!'); | |
const liveRegionNode = document.querySelector( | |
`[id^="live-region-"]`, | |
) as Element; | |
jest.advanceTimersByTime(499); | |
expect(liveRegionNode.textContent).toBe(''); | |
jest.advanceTimersByTime(1); | |
expect(liveRegionNode.textContent).toBe('delayed message!'); | |
}); | |
test('supports custom parent node', () => { | |
const parent = document.createElement('div'); | |
document.body.appendChild(parent); | |
const liveRegion = new OnDemandLiveRegion({ parent }); | |
liveRegion.say('hi from custom parent!'); | |
const liveRegionNode = parent.querySelector( | |
`[id^="live-region-"]`, | |
) as Element; | |
jest.advanceTimersByTime(0); | |
expect(liveRegionNode.textContent).toBe('hi from custom parent!'); | |
}); | |
test('supports custom live region node id prefix', () => { | |
const liveRegion = new OnDemandLiveRegion({ idPrefix: 'my-prefix-' }); | |
liveRegion.say('hi with custom id prefix!'); | |
const liveRegionNode = document.querySelector( | |
`[id^="my-prefix-"]`, | |
) as Element; | |
jest.advanceTimersByTime(0); | |
expect(liveRegionNode.textContent).toBe('hi with custom id prefix!'); | |
}); | |
test('removes old live region node if prompted to announce again', () => { | |
const liveRegion = new OnDemandLiveRegion(); | |
liveRegion.say('hi!'); | |
liveRegion.say('hello there!'); | |
const liveRegionNodes = document.querySelectorAll(`[id^="live-region-"]`); | |
expect(liveRegionNodes).toHaveLength(1); | |
}); | |
test('removes old live region node if explicitly cleared', () => { | |
const liveRegion = new OnDemandLiveRegion(); | |
liveRegion.say('hi!'); | |
liveRegion.clearNode(); | |
const liveRegionNodes = document.querySelectorAll(`[id^="live-region-"]`); | |
expect(liveRegionNodes).toHaveLength(0); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Based off https://github.com/Heydon/on-demand-live-region | |
* | |
* This class can be used for making screen readers announce text on demand, | |
* without a visual change to the interface. | |
* | |
* Example usage: | |
* const liveRegionPolite = new OnDemandLiveRegion(); | |
* const liveRegionAssertive = new OnDemandLiveRegion({ level: 'assertive' }); | |
* liveRegionPolite.say('Downloading'); | |
* liveRegionAssertive.say('Failed adding song to playing'); | |
*/ | |
export type OnDemandLiveRegionSettings = { | |
level: 'polite' | 'assertive'; | |
parent: HTMLElement; | |
idPrefix: string; | |
delay: number; | |
}; | |
export class OnDemandLiveRegion { | |
private settings: OnDemandLiveRegionSettings; | |
private currentRegion: HTMLSpanElement; | |
constructor(options: Partial<OnDemandLiveRegionSettings> = {}) { | |
// The default settings for the module. | |
this.settings = { | |
level: options.level || 'polite', | |
parent: options.parent || document.body, | |
idPrefix: options.idPrefix || 'live-region-', | |
delay: options.delay || 0, | |
}; | |
this.currentRegion = document.createElement('span'); | |
} | |
say(thingToSay: string, delay: number = this.settings.delay) { | |
this.clearNode(); | |
// Create fresh live region | |
this.currentRegion = document.createElement('span'); | |
this.currentRegion.id = | |
this.settings.idPrefix + Math.floor(Math.random() * 10000); | |
// Determine redundant role | |
const role = this.settings.level !== 'assertive' ? 'status' : 'alert'; | |
// Add role and aria-live attribution | |
this.currentRegion.setAttribute('aria-live', this.settings.level); | |
this.currentRegion.setAttribute('role', role); | |
// Hide live region element, but not from assistive technologies | |
this.currentRegion.setAttribute( | |
'style', | |
'clip: rect(1px, 1px, 1px, 1px); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px', | |
); | |
// Add live region to its designated parent | |
this.settings.parent.appendChild(this.currentRegion); | |
// Populate live region to trigger it | |
window.setTimeout(() => { | |
this.currentRegion.textContent = thingToSay; | |
}, delay); | |
} | |
clearNode() { | |
// Get rid of old live region if it exists | |
const oldRegion = this.settings.parent.querySelector( | |
`[id^="${this.settings.idPrefix}"]`, | |
); | |
if (oldRegion) { | |
this.settings.parent.removeChild(oldRegion); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment