@@ -141,51 +141,66 @@ public function toHtml(): string
141
141
$ htmlAttributes = '' ;
142
142
$ innerSvg = $ this ->innerSvg ;
143
143
$ attributes = $ this ->attributes ;
144
-
144
+
145
145
// Extract and remove title/desc attributes if present
146
146
$ title = $ attributes ['title ' ] ?? null ;
147
147
$ desc = $ attributes ['desc ' ] ?? null ;
148
148
unset($ attributes ['title ' ], $ attributes ['desc ' ]);
149
-
150
- // Prepare <title> and <desc> elements
149
+
151
150
$ labelledByIds = [];
152
151
$ a11yContent = '' ;
153
-
152
+
153
+ // Check if aria-labelledby should be added automatically
154
+ $ shouldSetLabelledBy = !isset ($ attributes ['aria-labelledby ' ]) && ($ title || $ desc );
155
+
154
156
if ($ title ) {
155
- $ titleId = 'title- ' . bin2hex (random_bytes (4 ));
156
- $ labelledByIds [] = $ titleId ;
157
- $ a11yContent .= sprintf ('<title id="%s">%s</title> ' , $ titleId , htmlspecialchars ((string ) $ title , ENT_QUOTES ));
157
+ if ($ shouldSetLabelledBy ) {
158
+ $ titleId = 'title- ' . bin2hex (random_bytes (4 ));
159
+ $ labelledByIds [] = $ titleId ;
160
+ $ a11yContent .= sprintf ('<title id="%s">%s</title> ' , $ titleId , htmlspecialchars ((string ) $ title , ENT_QUOTES ));
161
+ } else {
162
+ $ a11yContent .= sprintf ('<title>%s</title> ' , htmlspecialchars ((string ) $ title , ENT_QUOTES ));
163
+ }
158
164
}
159
-
165
+
160
166
if ($ desc ) {
161
- $ descId = 'desc- ' . bin2hex (random_bytes (4 ));
162
- $ labelledByIds [] = $ descId ;
163
- $ a11yContent .= sprintf ('<desc id="%s">%s</desc> ' , $ descId , htmlspecialchars ((string ) $ desc , ENT_QUOTES ));
167
+ if ($ shouldSetLabelledBy ) {
168
+ $ descId = 'desc- ' . bin2hex (random_bytes (4 ));
169
+ $ labelledByIds [] = $ descId ;
170
+ $ a11yContent .= sprintf ('<desc id="%s">%s</desc> ' , $ descId , htmlspecialchars ((string ) $ desc , ENT_QUOTES ));
171
+ } else {
172
+ $ a11yContent .= sprintf ('<desc>%s</desc> ' , htmlspecialchars ((string ) $ desc , ENT_QUOTES ));
173
+ }
164
174
}
165
-
166
- // Only add aria-labelledby if not already present and we have content
167
- if ($ a11yContent !== '' && !isset ($ attributes ['aria-labelledby ' ])) {
175
+
176
+ if ($ shouldSetLabelledBy ) {
168
177
$ attributes ['aria-labelledby ' ] = implode (' ' , $ labelledByIds );
169
178
}
170
-
179
+
171
180
// Build final attributes string
172
181
foreach ($ attributes as $ name => $ value ) {
173
- if (false === $ value ) {
182
+ if ($ value === false ) {
174
183
continue ;
175
184
}
176
-
177
- if (true === $ value && str_starts_with ($ name , 'aria- ' )) {
185
+
186
+ // Special case for aria-* attributes
187
+ // https://www.w3.org/TR/wai-aria-1.1/#state_prop_def
188
+ if ($ value === true && str_starts_with ($ name , 'aria- ' )) {
178
189
$ value = 'true ' ;
179
190
}
180
-
181
- $ htmlAttributes .= ' ' . $ name ;
182
-
183
- if (true === $ value ) {
191
+
192
+ $ htmlAttributes .= ' ' . $ name ;
193
+
194
+ if ($ value === true ) {
184
195
continue ;
185
196
}
186
-
187
- $ value = htmlspecialchars ((string ) $ value , ENT_QUOTES | ENT_SUBSTITUTE , 'UTF-8 ' );
188
- $ htmlAttributes .= '=" ' .$ value .'" ' ;
197
+
198
+ $ value = htmlspecialchars ($ value , \ENT_QUOTES | \ENT_SUBSTITUTE , 'UTF-8 ' );
199
+ $ htmlAttributes .= '=" ' . $ value . '" ' ;
200
+ }
201
+
202
+ // Inject <title> and <desc> before inner content
203
+ return '<svg ' . $ htmlAttributes . '> ' . $ a11yContent . $ innerSvg . '</svg> ' ;
189
204
}
190
205
191
206
public function getInnerSvg (): string
0 commit comments