Announce toasts with VoiceOver (#6250)

* Announce toasts with VoiceOver

Toasts are posted from a separate overlay window and never take
VoiceOver focus, so their text was never spoken. UserIndicatorController
now posts a high priority announcement with the title whenever a toast
with readable text is submitted, including re-submissions of an already
visible toast with the same ID. Modal indicators are not announced.

* Remove empty-title toast announcement check

Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com>

* Hide toasts from VoiceOver

The toast title is now announced by UserIndicatorController when the
toast is submitted, so letting VoiceOver also focus the toast itself
would only read the same text twice.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Mauro
2026-10-02 12:29:18 +02:00
committed by GitHub
parent f91b7441e7
commit d3604e8115
2 changed files with 13 additions and 0 deletions
@@ -44,6 +44,7 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
if let index = indicatorQueue.firstIndex(where: { $0.id == indicator.id }) {
indicatorQueue[index] = indicator
displayTimes[indicator.id] = .now
announceIfNeeded(indicator)
} else {
if let delay {
delayedIndicators.insert(indicator.id)
@@ -90,5 +91,16 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
retractIndicatorWithId(indicator.id)
indicatorQueue.append(indicator)
displayTimes[indicator.id] = .now
announceIfNeeded(indicator)
}
private func announceIfNeeded(_ indicator: UserIndicator) {
guard case .toast = indicator.type else {
return
}
var announcement = AttributedString(indicator.title)
announcement.accessibilitySpeechAnnouncementPriority = .high
AccessibilityNotification.Announcement(announcement).post()
}
}
@@ -33,6 +33,7 @@ struct UserIndicatorToastView: View {
.background(Color.compound.bgSubtlePrimary)
.clipShape(RoundedCornerShape(radius: 24.0, corners: .allCorners))
.shadow(color: .black.opacity(0.1), radius: 6.0, y: 4.0)
.accessibilityHidden(true)
.transition(toastTransition)
}