@@ -19,12 +19,17 @@ import {
19
19
model ,
20
20
signal ,
21
21
WritableSignal ,
22
- OnDestroy ,
23
22
} 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' ;
25
30
import { Directionality } from '@angular/cdk/bidi' ;
26
31
import { _IdGenerator } from '@angular/cdk/a11y' ;
27
- import { CdkToolbar } from '.. /toolbar' ;
32
+ import { CdkToolbarWidgetGroup } from '@angular/cdk-experimental /toolbar' ;
28
33
29
34
// TODO: Move mapSignal to it's own file so it can be reused across components.
30
35
@@ -91,43 +96,49 @@ export function mapSignal<T, V>(
91
96
'(pointerdown)' : 'pattern.onPointerdown($event)' ,
92
97
'(focusin)' : 'onFocus()' ,
93
98
} ,
99
+ hostDirectives : [
100
+ {
101
+ directive : CdkToolbarWidgetGroup ,
102
+ inputs : [ 'disabled' ] ,
103
+ } ,
104
+ ] ,
94
105
} )
95
106
export class CdkRadioGroup < V > {
96
107
/** A reference to the radio group element. */
97
108
private readonly _elementRef = inject ( ElementRef ) ;
98
109
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
+
99
116
/** The CdkRadioButtons nested inside of the CdkRadioGroup. */
100
117
private readonly _cdkRadioButtons = contentChildren ( CdkRadioButton , { descendants : true } ) ;
101
118
102
119
/** A signal wrapper for directionality. */
103
120
protected textDirection = inject ( Directionality ) . valueSignal ;
104
121
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
-
111
122
/** The RadioButton UIPatterns of the child CdkRadioButtons. */
112
123
protected items = computed ( ( ) => this . _cdkRadioButtons ( ) . map ( radio => radio . pattern ) ) ;
113
124
114
125
/** Whether the radio group is vertically or horizontally oriented. */
115
- orientation = input < 'vertical' | 'horizontal' > ( 'vertical' ) ;
126
+ readonly orientation = input < 'vertical' | 'horizontal' > ( 'vertical' ) ;
116
127
117
128
/** Whether disabled items in the group should be skipped when navigating. */
118
- skipDisabled = input ( true , { transform : booleanAttribute } ) ;
129
+ readonly skipDisabled = input ( true , { transform : booleanAttribute } ) ;
119
130
120
131
/** The focus strategy used by the radio group. */
121
- focusMode = input < 'roving' | 'activedescendant' > ( 'roving' ) ;
132
+ readonly focusMode = input < 'roving' | 'activedescendant' > ( 'roving' ) ;
122
133
123
134
/** Whether the radio group is disabled. */
124
- disabled = input ( false , { transform : booleanAttribute } ) ;
135
+ readonly disabled = input ( false , { transform : booleanAttribute } ) ;
125
136
126
137
/** Whether the radio group is readonly. */
127
- readonly = input ( false , { transform : booleanAttribute } ) ;
138
+ readonly readonly = input ( false , { transform : booleanAttribute } ) ;
128
139
129
140
/** The value of the currently selected radio button. */
130
- value = model < V | null > ( null ) ;
141
+ readonly value = model < V | null > ( null ) ;
131
142
132
143
/** The internal selection state for the radio group. */
133
144
private readonly _value = mapSignal < V | null , V [ ] > ( this . value , {
@@ -136,22 +147,37 @@ export class CdkRadioGroup<V> {
136
147
} ) ;
137
148
138
149
/** 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 > ;
150
151
151
152
/** Whether the radio group has received focus yet. */
152
153
private _hasFocused = signal ( false ) ;
153
154
154
155
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
+
155
181
afterRenderEffect ( ( ) => {
156
182
if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
157
183
const violations = this . pattern . validate ( ) ;
@@ -162,35 +188,15 @@ export class CdkRadioGroup<V> {
162
188
} ) ;
163
189
164
190
afterRenderEffect ( ( ) => {
165
- if ( ! this . _hasFocused ( ) && ! this . toolbar ) {
191
+ if ( ! this . _hasFocused ( ) && ! this . _hasToolbar ( ) ) {
166
192
this . pattern . setDefaultState ( ) ;
167
193
}
168
194
} ) ;
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
- } ) ;
183
195
}
184
196
185
197
onFocus ( ) {
186
198
this . _hasFocused . set ( true ) ;
187
199
}
188
-
189
- toolbarButtonUnregister ( radio : CdkRadioButton < V > ) {
190
- if ( this . toolbar ) {
191
- this . toolbar . unregister ( radio ) ;
192
- }
193
- }
194
200
}
195
201
196
202
/** A selectable radio button in a CdkRadioGroup. */
@@ -207,7 +213,7 @@ export class CdkRadioGroup<V> {
207
213
'[id]' : 'pattern.id()' ,
208
214
} ,
209
215
} )
210
- export class CdkRadioButton < V > implements OnDestroy {
216
+ export class CdkRadioButton < V > {
211
217
/** A reference to the radio button element. */
212
218
private readonly _elementRef = inject ( ElementRef ) ;
213
219
@@ -218,13 +224,13 @@ export class CdkRadioButton<V> implements OnDestroy {
218
224
private readonly _generatedId = inject ( _IdGenerator ) . getId ( 'cdk-radio-button-' ) ;
219
225
220
226
/** A unique identifier for the radio button. */
221
- protected id = computed ( ( ) => this . _generatedId ) ;
227
+ readonly id = computed ( ( ) => this . _generatedId ) ;
222
228
223
229
/** The value associated with the radio button. */
224
230
readonly value = input . required < V > ( ) ;
225
231
226
232
/** The parent RadioGroup UIPattern. */
227
- protected group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
233
+ readonly group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
228
234
229
235
/** A reference to the radio button element to be focused on navigation. */
230
236
element = computed ( ( ) => this . _elementRef . nativeElement ) ;
@@ -240,10 +246,4 @@ export class CdkRadioButton<V> implements OnDestroy {
240
246
group : this . group ,
241
247
element : this . element ,
242
248
} ) ;
243
-
244
- ngOnDestroy ( ) {
245
- if ( this . _cdkRadioGroup . toolbar ) {
246
- this . _cdkRadioGroup . toolbarButtonUnregister ( this ) ;
247
- }
248
- }
249
249
}
0 commit comments