@@ -19,12 +19,16 @@ 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
+ RadioGroupPattern ,
26
+ RadioGroupInteraction ,
27
+ RadioGroupInstructionHandler ,
28
+ } from '../ui-patterns' ;
25
29
import { Directionality } from '@angular/cdk/bidi' ;
26
30
import { _IdGenerator } from '@angular/cdk/a11y' ;
27
- import { CdkToolbar } from '.. /toolbar' ;
31
+ import { CdkToolbarWidgetGroup } from '@angular/cdk-experimental /toolbar' ;
28
32
29
33
// TODO: Move mapSignal to it's own file so it can be reused across components.
30
34
@@ -87,27 +91,28 @@ export function mapSignal<T, V>(
87
91
'[attr.aria-disabled]' : 'pattern.disabled()' ,
88
92
'[attr.aria-orientation]' : 'pattern.orientation()' ,
89
93
'[attr.aria-activedescendant]' : 'pattern.activedescendant()' ,
90
- '(keydown)' : 'pattern. onKeydown($event)' ,
91
- '(pointerdown)' : 'pattern. onPointerdown($event)' ,
94
+ '(keydown)' : 'onKeydown($event)' ,
95
+ '(pointerdown)' : 'onPointerdown($event)' ,
92
96
'(focusin)' : 'onFocus()' ,
93
97
} ,
98
+ hostDirectives : [ CdkToolbarWidgetGroup ] ,
94
99
} )
95
100
export class CdkRadioGroup < V > {
96
101
/** A reference to the radio group element. */
97
102
private readonly _elementRef = inject ( ElementRef ) ;
98
103
104
+ /** A reference to the CdkToolbarWidgetGroup, if the radio group is in a toolbar. */
105
+ private readonly _cdkToolbarWidgetGroup = inject ( CdkToolbarWidgetGroup ) ;
106
+
107
+ /** Whether the radio group is inside of a CdkToolbar. */
108
+ private readonly _hasToolbar = computed ( ( ) => ! ! this . _cdkToolbarWidgetGroup . toolbar ( ) ) ;
109
+
99
110
/** The CdkRadioButtons nested inside of the CdkRadioGroup. */
100
111
private readonly _cdkRadioButtons = contentChildren ( CdkRadioButton , { descendants : true } ) ;
101
112
102
113
/** A signal wrapper for directionality. */
103
114
protected textDirection = inject ( Directionality ) . valueSignal ;
104
115
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
116
/** The RadioButton UIPatterns of the child CdkRadioButtons. */
112
117
protected items = computed ( ( ) => this . _cdkRadioButtons ( ) . map ( radio => radio . pattern ) ) ;
113
118
@@ -135,17 +140,36 @@ export class CdkRadioGroup<V> {
135
140
reverse : values => ( values . length === 0 ? null : values [ 0 ] ) ,
136
141
} ) ;
137
142
143
+ /**
144
+ * The effective orientation of the radio group
145
+ * taking the parent toolbar's orientation into account.
146
+ */
147
+ private _orientation = computed (
148
+ ( ) => this . _cdkToolbarWidgetGroup . toolbar ( ) ?. orientation ( ) ?? this . orientation ( ) ,
149
+ ) ;
150
+
151
+ /** The effective skipDisabled behavior, taking the parent toolbar's setting into account. */
152
+ private _skipDisabled = computed (
153
+ ( ) => this . _cdkToolbarWidgetGroup . toolbar ( ) ?. skipDisabled ( ) ?? this . skipDisabled ( ) ,
154
+ ) ;
155
+
138
156
/** The RadioGroup UIPattern. */
139
- pattern : RadioGroupPattern < V > = new RadioGroupPattern < V > ( {
157
+ readonly pattern : RadioGroupPattern < V > = new RadioGroupPattern < V > ( {
140
158
...this ,
141
159
items : this . items ,
142
160
value : this . _value ,
143
161
activeItem : signal ( undefined ) ,
162
+ orientation : this . _orientation ,
144
163
textDirection : this . textDirection ,
145
- toolbar : this . _toolbarPattern ,
164
+ skipDisabled : this . _skipDisabled ,
146
165
element : ( ) => this . _elementRef . nativeElement ,
147
- focusMode : this . _toolbarPattern ( ) ?. inputs . focusMode ?? this . focusMode ,
148
- skipDisabled : this . _toolbarPattern ( ) ?. inputs . skipDisabled ?? this . skipDisabled ,
166
+ } ) ;
167
+
168
+ /** The interaction manager for the radio group, which translates DOM events into instructions. */
169
+ readonly interaction = new RadioGroupInteraction ( {
170
+ orientation : this . _orientation ,
171
+ textDirection : this . textDirection ,
172
+ handler : ( ) => ( i => this . pattern . execute ( i ) ) as RadioGroupInstructionHandler ,
149
173
} ) ;
150
174
151
175
/** Whether the radio group has received focus yet. */
@@ -162,34 +186,34 @@ export class CdkRadioGroup<V> {
162
186
} ) ;
163
187
164
188
afterRenderEffect ( ( ) => {
165
- if ( ! this . _hasFocused ( ) && ! this . toolbar ) {
189
+ if ( ! this . _hasFocused ( ) && ! this . _hasToolbar ( ) ) {
166
190
this . pattern . setDefaultState ( ) ;
167
191
}
168
192
} ) ;
169
193
170
- // TODO: Refactor to be handled within list behavior
171
194
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
- }
195
+ if ( this . _hasToolbar ( ) ) {
196
+ this . _cdkToolbarWidgetGroup . disabled . set ( this . disabled ( ) ) ;
181
197
}
182
198
} ) ;
199
+
200
+ if ( this . _hasToolbar ( ) ) {
201
+ this . _cdkToolbarWidgetGroup . handler . set ( i => this . pattern . toolbarExecute ( i ) ) ;
202
+ }
183
203
}
184
204
185
205
onFocus ( ) {
186
206
this . _hasFocused . set ( true ) ;
187
207
}
188
208
189
- toolbarButtonUnregister ( radio : CdkRadioButton < V > ) {
190
- if ( this . toolbar ) {
191
- this . toolbar . unregister ( radio ) ;
192
- }
209
+ onKeydown ( event : KeyboardEvent ) {
210
+ if ( this . _hasToolbar ( ) ) return ;
211
+ this . interaction . onKeydown ( event ) ;
212
+ }
213
+
214
+ onPointerdown ( event : PointerEvent ) {
215
+ if ( this . _hasToolbar ( ) ) return ;
216
+ this . interaction . onPointerdown ( event ) ;
193
217
}
194
218
}
195
219
@@ -207,7 +231,7 @@ export class CdkRadioGroup<V> {
207
231
'[id]' : 'pattern.id()' ,
208
232
} ,
209
233
} )
210
- export class CdkRadioButton < V > implements OnDestroy {
234
+ export class CdkRadioButton < V > {
211
235
/** A reference to the radio button element. */
212
236
private readonly _elementRef = inject ( ElementRef ) ;
213
237
@@ -218,13 +242,13 @@ export class CdkRadioButton<V> implements OnDestroy {
218
242
private readonly _generatedId = inject ( _IdGenerator ) . getId ( 'cdk-radio-button-' ) ;
219
243
220
244
/** A unique identifier for the radio button. */
221
- protected id = computed ( ( ) => this . _generatedId ) ;
245
+ readonly id = computed ( ( ) => this . _generatedId ) ;
222
246
223
247
/** The value associated with the radio button. */
224
248
readonly value = input . required < V > ( ) ;
225
249
226
250
/** The parent RadioGroup UIPattern. */
227
- protected group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
251
+ readonly group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
228
252
229
253
/** A reference to the radio button element to be focused on navigation. */
230
254
element = computed ( ( ) => this . _elementRef . nativeElement ) ;
@@ -240,10 +264,4 @@ export class CdkRadioButton<V> implements OnDestroy {
240
264
group : this . group ,
241
265
element : this . element ,
242
266
} ) ;
243
-
244
- ngOnDestroy ( ) {
245
- if ( this . _cdkRadioGroup . toolbar ) {
246
- this . _cdkRadioGroup . toolbarButtonUnregister ( this ) ;
247
- }
248
- }
249
267
}
0 commit comments