@@ -19,12 +19,17 @@ import {
1919 model ,
2020 signal ,
2121 WritableSignal ,
22- OnDestroy ,
2322} from '@angular/core' ;
24- import { RadioButtonPattern , RadioGroupPattern } from '../ui-patterns' ;
23+ import {
24+ RadioButtonPattern ,
25+ RadioGroupInputs ,
26+ RadioGroupPattern ,
27+ ToolbarRadioGroupInputs ,
28+ ToolbarRadioGroupPattern ,
29+ } from '../ui-patterns' ;
2530import { Directionality } from '@angular/cdk/bidi' ;
2631import { _IdGenerator } from '@angular/cdk/a11y' ;
27- import { CdkToolbar } from '.. /toolbar' ;
32+ import { CdkToolbarWidgetGroup } from '@angular/cdk-experimental /toolbar' ;
2833
2934// TODO: Move mapSignal to it's own file so it can be reused across components.
3035
@@ -91,43 +96,49 @@ export function mapSignal<T, V>(
9196 '(pointerdown)' : 'pattern.onPointerdown($event)' ,
9297 '(focusin)' : 'onFocus()' ,
9398 } ,
99+ hostDirectives : [
100+ {
101+ directive : CdkToolbarWidgetGroup ,
102+ inputs : [ 'disabled' ] ,
103+ } ,
104+ ] ,
94105} )
95106export class CdkRadioGroup < V > {
96107 /** A reference to the radio group element. */
97108 private readonly _elementRef = inject ( ElementRef ) ;
98109
110+ /** A reference to the CdkToolbarWidgetGroup, if the radio group is in a toolbar. */
111+ private readonly _cdkToolbarWidgetGroup = inject ( CdkToolbarWidgetGroup ) ;
112+
113+ /** Whether the radio group is inside of a CdkToolbar. */
114+ private readonly _hasToolbar = computed ( ( ) => ! ! this . _cdkToolbarWidgetGroup . toolbar ( ) ) ;
115+
99116 /** The CdkRadioButtons nested inside of the CdkRadioGroup. */
100117 private readonly _cdkRadioButtons = contentChildren ( CdkRadioButton , { descendants : true } ) ;
101118
102119 /** A signal wrapper for directionality. */
103120 protected textDirection = inject ( Directionality ) . valueSignal ;
104121
105- /** A signal wrapper for toolbar. */
106- toolbar = inject ( CdkToolbar , { optional : true } ) ;
107-
108- /** Toolbar pattern if applicable */
109- private readonly _toolbarPattern = computed ( ( ) => this . toolbar ?. pattern ) ;
110-
111122 /** The RadioButton UIPatterns of the child CdkRadioButtons. */
112123 protected items = computed ( ( ) => this . _cdkRadioButtons ( ) . map ( radio => radio . pattern ) ) ;
113124
114125 /** Whether the radio group is vertically or horizontally oriented. */
115- orientation = input < 'vertical' | 'horizontal' > ( 'vertical' ) ;
126+ readonly orientation = input < 'vertical' | 'horizontal' > ( 'vertical' ) ;
116127
117128 /** Whether disabled items in the group should be skipped when navigating. */
118- skipDisabled = input ( true , { transform : booleanAttribute } ) ;
129+ readonly skipDisabled = input ( true , { transform : booleanAttribute } ) ;
119130
120131 /** The focus strategy used by the radio group. */
121- focusMode = input < 'roving' | 'activedescendant' > ( 'roving' ) ;
132+ readonly focusMode = input < 'roving' | 'activedescendant' > ( 'roving' ) ;
122133
123134 /** Whether the radio group is disabled. */
124- disabled = input ( false , { transform : booleanAttribute } ) ;
135+ readonly disabled = input ( false , { transform : booleanAttribute } ) ;
125136
126137 /** Whether the radio group is readonly. */
127- readonly = input ( false , { transform : booleanAttribute } ) ;
138+ readonly readonly = input ( false , { transform : booleanAttribute } ) ;
128139
129140 /** The value of the currently selected radio button. */
130- value = model < V | null > ( null ) ;
141+ readonly value = model < V | null > ( null ) ;
131142
132143 /** The internal selection state for the radio group. */
133144 private readonly _value = mapSignal < V | null , V [ ] > ( this . value , {
@@ -136,22 +147,37 @@ export class CdkRadioGroup<V> {
136147 } ) ;
137148
138149 /** The RadioGroup UIPattern. */
139- pattern : RadioGroupPattern < V > = new RadioGroupPattern < V > ( {
140- ...this ,
141- items : this . items ,
142- value : this . _value ,
143- activeItem : signal ( undefined ) ,
144- textDirection : this . textDirection ,
145- toolbar : this . _toolbarPattern ,
146- element : ( ) => this . _elementRef . nativeElement ,
147- focusMode : this . _toolbarPattern ( ) ?. inputs . focusMode ?? this . focusMode ,
148- skipDisabled : this . _toolbarPattern ( ) ?. inputs . skipDisabled ?? this . skipDisabled ,
149- } ) ;
150+ readonly pattern : RadioGroupPattern < V > ;
150151
151152 /** Whether the radio group has received focus yet. */
152153 private _hasFocused = signal ( false ) ;
153154
154155 constructor ( ) {
156+ const inputs : RadioGroupInputs < V > | ToolbarRadioGroupInputs < V > = {
157+ ...this ,
158+ items : this . items ,
159+ value : this . _value ,
160+ activeItem : signal ( undefined ) ,
161+ textDirection : this . textDirection ,
162+ element : ( ) => this . _elementRef . nativeElement ,
163+ getItem : e => {
164+ if ( ! ( e . target instanceof HTMLElement ) ) {
165+ return undefined ;
166+ }
167+ const element = e . target . closest ( '[role="radio"]' ) ;
168+ return this . items ( ) . find ( i => i . element ( ) === element ) ;
169+ } ,
170+ toolbar : this . _cdkToolbarWidgetGroup . toolbar ,
171+ } ;
172+
173+ this . pattern = this . _hasToolbar ( )
174+ ? new ToolbarRadioGroupPattern < V > ( inputs as ToolbarRadioGroupInputs < V > )
175+ : new RadioGroupPattern < V > ( inputs as RadioGroupInputs < V > ) ;
176+
177+ if ( this . _hasToolbar ( ) ) {
178+ this . _cdkToolbarWidgetGroup . controls . set ( this . pattern as ToolbarRadioGroupPattern < V > ) ;
179+ }
180+
155181 afterRenderEffect ( ( ) => {
156182 if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
157183 const violations = this . pattern . validate ( ) ;
@@ -162,35 +188,15 @@ export class CdkRadioGroup<V> {
162188 } ) ;
163189
164190 afterRenderEffect ( ( ) => {
165- if ( ! this . _hasFocused ( ) && ! this . toolbar ) {
191+ if ( ! this . _hasFocused ( ) && ! this . _hasToolbar ( ) ) {
166192 this . pattern . setDefaultState ( ) ;
167193 }
168194 } ) ;
169-
170- // TODO: Refactor to be handled within list behavior
171- afterRenderEffect ( ( ) => {
172- if ( this . toolbar ) {
173- const radioButtons = this . _cdkRadioButtons ( ) ;
174- // If the group is disabled and the toolbar is set to skip disabled items,
175- // the radio buttons should not be part of the toolbar's navigation.
176- if ( this . disabled ( ) && this . toolbar . skipDisabled ( ) ) {
177- radioButtons . forEach ( radio => this . toolbar ! . unregister ( radio ) ) ;
178- } else {
179- radioButtons . forEach ( radio => this . toolbar ! . register ( radio ) ) ;
180- }
181- }
182- } ) ;
183195 }
184196
185197 onFocus ( ) {
186198 this . _hasFocused . set ( true ) ;
187199 }
188-
189- toolbarButtonUnregister ( radio : CdkRadioButton < V > ) {
190- if ( this . toolbar ) {
191- this . toolbar . unregister ( radio ) ;
192- }
193- }
194200}
195201
196202/** A selectable radio button in a CdkRadioGroup. */
@@ -207,7 +213,7 @@ export class CdkRadioGroup<V> {
207213 '[id]' : 'pattern.id()' ,
208214 } ,
209215} )
210- export class CdkRadioButton < V > implements OnDestroy {
216+ export class CdkRadioButton < V > {
211217 /** A reference to the radio button element. */
212218 private readonly _elementRef = inject ( ElementRef ) ;
213219
@@ -218,13 +224,13 @@ export class CdkRadioButton<V> implements OnDestroy {
218224 private readonly _generatedId = inject ( _IdGenerator ) . getId ( 'cdk-radio-button-' ) ;
219225
220226 /** A unique identifier for the radio button. */
221- protected id = computed ( ( ) => this . _generatedId ) ;
227+ readonly id = computed ( ( ) => this . _generatedId ) ;
222228
223229 /** The value associated with the radio button. */
224230 readonly value = input . required < V > ( ) ;
225231
226232 /** The parent RadioGroup UIPattern. */
227- protected group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
233+ readonly group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
228234
229235 /** A reference to the radio button element to be focused on navigation. */
230236 element = computed ( ( ) => this . _elementRef . nativeElement ) ;
@@ -240,10 +246,4 @@ export class CdkRadioButton<V> implements OnDestroy {
240246 group : this . group ,
241247 element : this . element ,
242248 } ) ;
243-
244- ngOnDestroy ( ) {
245- if ( this . _cdkRadioGroup . toolbar ) {
246- this . _cdkRadioGroup . toolbarButtonUnregister ( this ) ;
247- }
248- }
249249}
0 commit comments