@@ -106,14 +106,14 @@ export class CdkAccordionTrigger {
106106 /** A global unique identifier for the trigger. */
107107 private readonly _id = inject ( _IdGenerator ) . getId ( 'cdk-accordion-trigger-' ) ;
108108
109+ /** A computed signal to generate a consistent ID for the visually hidden label. */
110+ private readonly _visuallyHiddenId = inject ( _IdGenerator ) . getId ( 'cdk-accordion-label-' ) ;
111+
109112 /** A reference to the trigger element. */
110113 private readonly _elementRef = inject ( ElementRef ) ;
111114
112115 private readonly _renderer = inject ( Renderer2 ) ;
113116
114- /** A computed signal to generate a consistent ID for the visually hidden label. */
115- readonly visuallyHiddenId = computed ( ( ) => this . pattern . id ( ) + '-label' ) ;
116-
117117 /** The parent CdkAccordionGroup. */
118118 private readonly _accordionGroup = inject ( CdkAccordionGroup ) ;
119119
@@ -136,43 +136,65 @@ export class CdkAccordionTrigger {
136136 /** The UI pattern instance for this trigger. */
137137 readonly pattern : AccordionTriggerPattern = new AccordionTriggerPattern ( {
138138 id : ( ) => this . _id ,
139+ visuallyHiddenId : ( ) => this . _visuallyHiddenId ,
139140 value : this . value ,
140141 disabled : this . disabled ,
141142 element : ( ) => this . _elementRef . nativeElement ,
142143 accordionGroup : computed ( ( ) => this . _accordionGroup . pattern ) ,
143144 accordionPanel : this . accordionPanel ,
144145 } ) ;
145146
146- // Creating the visuallyHiddenSpan as an accessible reference for the accordion content
147+ /** The computed label value of this Accordion Trigger to be passed to a visually hidden
148+ * span that is accessible to screen readers whether the button is disabled or not.
149+ */
150+ readonly visuallyHiddenLabel = computed ( ( ) => {
151+ let buttonText = '' ;
152+ const buttonElement = this . _elementRef . nativeElement ;
153+ for ( const node of Array . from ( buttonElement . childNodes ) ) {
154+ if ( node instanceof Node && node . nodeType === Node . TEXT_NODE ) {
155+ buttonText += ( node as Text ) . textContent ?. trim ( ) + ' ' ;
156+ }
157+ }
158+
159+ // Determine the state labels of the Accordion Trigger to pass to the label
160+ const expansionLabel = this . pattern . expanded ( ) ? '(Expanded)' : '(Collapsed)' ;
161+ const disabledLabel = this . pattern . disabled ( ) ? '(Disabled)' : '' ;
162+
163+ // Combine all parts into the final label
164+ return `${ buttonText . trim ( ) } ${ expansionLabel } ${ disabledLabel } ` . trim ( ) ;
165+ } ) ;
166+
147167 constructor ( ) {
148- // We'll use afterRenderEffect to ensure the element is created after the host element.
149168 afterRenderEffect ( ( ) => {
150- // Find the button element and its parent
151169 const buttonElement = this . _elementRef . nativeElement ;
152170 const parentElement = this . _renderer . parentNode ( buttonElement ) ;
153171
154172 if ( parentElement ) {
155- // Create a new visually hidden span element to be referenced by accordionPanel
156- const visuallyHiddenSpan = this . _renderer . createElement ( 'span' ) ;
157- this . _renderer . addClass ( visuallyHiddenSpan , 'cdk-visually-hidden' ) ;
158- this . _renderer . setAttribute ( visuallyHiddenSpan , 'id' , this . pattern . visuallyHiddenId ( ) ) ;
159- this . _renderer . setAttribute ( visuallyHiddenSpan , 'tabindex' , '-1' ) ;
160-
161- // Get the button's text content and set it on the span
162- let buttonText = '' ;
163- for ( const node of Array . from ( buttonElement . childNodes ) ) {
164- if ( node instanceof Node && node . nodeType === Node . TEXT_NODE ) {
165- buttonText += node . textContent ?. trim ( ) + ' ' ;
166- }
173+ // Create the span and attach it to the DOM only once.
174+ if ( ! this . _visuallyHiddenSpan ) {
175+ this . _visuallyHiddenSpan = this . _renderer . createElement ( 'span' ) ;
176+ this . _renderer . addClass ( this . _visuallyHiddenSpan , 'cdk-visually-hidden' ) ;
177+ this . _renderer . setAttribute (
178+ this . _visuallyHiddenSpan ,
179+ 'id' ,
180+ this . pattern . visuallyHiddenId ( ) ,
181+ ) ;
182+ this . _renderer . setAttribute ( this . _visuallyHiddenSpan , 'tabindex' , '-1' ) ;
183+ this . _renderer . insertBefore ( parentElement , this . _visuallyHiddenSpan , buttonElement ) ;
167184 }
168- const textNode = this . _renderer . createText ( buttonText ) ;
169- this . _renderer . appendChild ( visuallyHiddenSpan , textNode ) ;
170185
171- // Insert the visually hidden span before the button, as its sibling
172- this . _renderer . insertBefore ( parentElement , visuallyHiddenSpan , buttonElement ) ;
186+ // Update its text content whenever the signal changes.
187+ this . _renderer . setProperty (
188+ this . _visuallyHiddenSpan ,
189+ 'textContent' ,
190+ this . visuallyHiddenLabel ( ) ,
191+ ) ;
173192 }
174193 } ) ;
175194 }
195+
196+ // Add a private property to store a reference to the span
197+ private _visuallyHiddenSpan ! : HTMLElement ;
176198}
177199
178200/**
0 commit comments