(element)
| 137 | * > aria-describedby > hybrid question text (for radio/checkbox) > nearby text fallback |
| 138 | */ |
| 139 | export function getLabelForElement(element) { |
| 140 | if (!element) return ''; |
| 141 | |
| 142 | // Strategy 1: Explicit <label for="id"> |
| 143 | if (element.id) { |
| 144 | const label = document.querySelector(`label[for="${CSS.escape(element.id)}"]`); |
| 145 | if (label) return cleanLabelText(label, element); |
| 146 | } |
| 147 | |
| 148 | // Strategy 2: aria-labelledby (references another element) |
| 149 | const labelledBy = element.getAttribute('aria-labelledby'); |
| 150 | if (labelledBy) { |
| 151 | const labelEl = document.getElementById(labelledBy); |
| 152 | if (labelEl) return cleanLabelText(labelEl, element); |
| 153 | } |
| 154 | |
| 155 | // Strategy 3: aria-label attribute |
| 156 | const ariaLabel = element.getAttribute('aria-label'); |
| 157 | if (ariaLabel) return ariaLabel.trim(); |
| 158 | |
| 159 | // Strategy 4: Implicit label (element wrapped in <label>) |
| 160 | const parentLabel = element.closest('label'); |
| 161 | if (parentLabel) return cleanLabelText(parentLabel, element); |
| 162 | |
| 163 | // Strategy 5: Placeholder (fallback for inputs) |
| 164 | if (element.placeholder) return element.placeholder.trim(); |
| 165 | |
| 166 | // Strategy 6: Legend in fieldset |
| 167 | const fieldset = element.closest('fieldset'); |
| 168 | if (fieldset) { |
| 169 | const legend = fieldset.querySelector('legend'); |
| 170 | if (legend) return cleanLabelText(legend, element); |
| 171 | } |
| 172 | |
| 173 | // Strategy 7: aria-describedby (common for questions) |
| 174 | const describedBy = element.getAttribute('aria-describedby'); |
| 175 | if (describedBy) { |
| 176 | const descEl = document.getElementById(describedBy); |
| 177 | if (descEl) return cleanLabelText(descEl, element); |
| 178 | } |
| 179 | |
| 180 | // Strategy 8: For radio/checkbox - find question using hybrid approach |
| 181 | if (element.type === 'radio' || element.type === 'checkbox') { |
| 182 | const questionText = findQuestionText(element); |
| 183 | if (questionText) return questionText; |
| 184 | } |
| 185 | |
| 186 | // Strategy 9: General fallback - DOM traversal for nearby text |
| 187 | const nearbyText = findNearbyLabelText(element); |
| 188 | if (nearbyText) return nearbyText; |
| 189 | |
| 190 | return ''; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Clean label text - remove element's own value and extra whitespace |
no test coverage detected