screen reader grid and readout shortcuts

This commit is contained in:
2026-08-20 14:20:00 +03:00
parent ae28fd2dcb
commit 4689cb5971
7 changed files with 151 additions and 20 deletions
+38 -5
View File
@@ -4,6 +4,7 @@ export interface NarratorSettings {
pitch: number
voice: string
verbosity: 'brief' | 'normal' | 'verbose'
mode: 'voice' | 'screenreader' | 'both'
sounds: boolean
}
@@ -13,6 +14,7 @@ export const defaultNarratorSettings: NarratorSettings = {
pitch: 1.0,
voice: '',
verbosity: 'normal',
mode: 'voice',
sounds: true,
}
@@ -36,8 +38,42 @@ export function cancel() {
window.speechSynthesis.cancel()
}
// a polite live region that screen readers announce from, created on demand
let srRegion: HTMLElement | null = null
function getSrRegion(): HTMLElement {
if (!srRegion) {
srRegion = document.createElement('div')
srRegion.setAttribute('aria-live', 'polite')
srRegion.setAttribute('role', 'status')
srRegion.className = 'visually-hidden'
document.body.appendChild(srRegion)
}
return srRegion
}
function announceToScreenReader(text: string) {
const region = getSrRegion()
const msg = document.createElement('span')
msg.textContent = `${text} `
region.appendChild(msg)
while (region.children.length > 6) {
const first = region.firstChild
if (first) region.removeChild(first)
}
setTimeout(() => msg.remove(), 15000)
}
export function speak(text: string, interrupt = true) {
if (!isSupported() || !settings.enabled) return
if (!settings.enabled) return
const mode = settings.mode
const wantsVoice = (mode === 'voice' || mode === 'both') && isSupported()
const wantsSr = mode === 'screenreader' || mode === 'both' || !isSupported()
if (wantsSr) announceToScreenReader(text)
if (!wantsVoice) return
if (interrupt) cancel()
const utterance = new SpeechSynthesisUtterance(text)
@@ -57,9 +93,6 @@ export function speak(text: string, interrupt = true) {
export function preloadVoices() {
if (!isSupported()) return
getVoices()
if (window.speechSynthesis.onvoiceschanged !== undefined) {
window.speechSynthesis.onvoiceschanged = () => {}
}
}
preloadVoices()
preloadVoices()