|
67 | 67 | * @param diameter Base diameter of the wheel in pixels |
68 | 68 | * @returns void |
69 | 69 | */ |
| 70 | +// =============================== |
| 71 | +// Accessibility: Speech Support |
| 72 | +// =============================== |
| 73 | + |
| 74 | +let speechEnabled = false; |
| 75 | + |
| 76 | +/** |
| 77 | + * Toggle speech feedback for pie menus |
| 78 | + * @param {boolean} enabled |
| 79 | + */ |
| 80 | +export function setPieMenuSpeech(enabled) { |
| 81 | + speechEnabled = enabled; |
| 82 | +} |
| 83 | + |
| 84 | +/** |
| 85 | + * Speak a label using Web Speech API |
| 86 | + * Safe wrapper with fallbacks |
| 87 | + * @param {string} text |
| 88 | + */ |
| 89 | +function speakLabel(text) { |
| 90 | + if (!speechEnabled) return; |
| 91 | + if (!("speechSynthesis" in window)) return; |
| 92 | + if (!text) return; |
| 93 | + |
| 94 | + try { |
| 95 | + // Cancel any ongoing speech to prevent overlap |
| 96 | + window.speechSynthesis.cancel(); |
| 97 | + |
| 98 | + const utterance = new SpeechSynthesisUtterance(text); |
| 99 | + utterance.rate = 1; |
| 100 | + utterance.pitch = 1; |
| 101 | + utterance.volume = 1; |
| 102 | + |
| 103 | + window.speechSynthesis.speak(utterance); |
| 104 | + |
| 105 | + updateAriaLiveRegion(text); |
| 106 | + } catch (err) { |
| 107 | + console.warn("Speech synthesis error:", err); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +/** |
| 112 | + * Update aria-live region for screen readers |
| 113 | + * @param {string} text |
| 114 | + */ |
| 115 | +function updateAriaLiveRegion(text) { |
| 116 | + let region = document.getElementById("pie-menu-live-region"); |
| 117 | + |
| 118 | + if (!region) { |
| 119 | + region = document.createElement("div"); |
| 120 | + region.id = "pie-menu-live-region"; |
| 121 | + region.setAttribute("aria-live", "polite"); |
| 122 | + region.setAttribute("aria-atomic", "true"); |
| 123 | + region.style.position = "absolute"; |
| 124 | + region.style.left = "-9999px"; |
| 125 | + document.body.appendChild(region); |
| 126 | + } |
| 127 | + |
| 128 | + region.textContent = text; |
| 129 | +} |
70 | 130 | const setWheelSize = (i = 400) => { |
71 | 131 | const wheelDiv = document.getElementById("wheelDiv"); |
72 | 132 | const screenWidth = window.innerWidth; |
|
0 commit comments