@@ -141,51 +141,66 @@ public function toHtml(): string
141141 $ htmlAttributes = '' ;
142142 $ innerSvg = $ this ->innerSvg ;
143143 $ attributes = $ this ->attributes ;
144-
144+
145145 // Extract and remove title/desc attributes if present
146146 $ title = $ attributes ['title ' ] ?? null ;
147147 $ desc = $ attributes ['desc ' ] ?? null ;
148148 unset($ attributes ['title ' ], $ attributes ['desc ' ]);
149-
150- // Prepare <title> and <desc> elements
149+
151150 $ labelledByIds = [];
152151 $ a11yContent = '' ;
153-
152+
153+ // Check if aria-labelledby should be added automatically
154+ $ shouldSetLabelledBy = !isset ($ attributes ['aria-labelledby ' ]) && ($ title || $ desc );
155+
154156 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+ }
158164 }
159-
165+
160166 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+ }
164174 }
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 ) {
168177 $ attributes ['aria-labelledby ' ] = implode (' ' , $ labelledByIds );
169178 }
170-
179+
171180 // Build final attributes string
172181 foreach ($ attributes as $ name => $ value ) {
173- if (false === $ value ) {
182+ if ($ value === false ) {
174183 continue ;
175184 }
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- ' )) {
178189 $ value = 'true ' ;
179190 }
180-
181- $ htmlAttributes .= ' ' . $ name ;
182-
183- if (true === $ value ) {
191+
192+ $ htmlAttributes .= ' ' . $ name ;
193+
194+ if ($ value === true ) {
184195 continue ;
185196 }
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> ' ;
189204 }
190205
191206 public function getInnerSvg (): string
0 commit comments