@@ -19,12 +19,16 @@ 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+ RadioGroupPattern ,
26+ RadioGroupInteraction ,
27+ RadioGroupInstructionHandler ,
28+ } from '../ui-patterns' ;
2529import { Directionality } from '@angular/cdk/bidi' ;
2630import { _IdGenerator } from '@angular/cdk/a11y' ;
27- import { CdkToolbar } from '.. /toolbar' ;
31+ import { CdkToolbarWidgetGroup } from '@angular/cdk-experimental /toolbar' ;
2832
2933// TODO: Move mapSignal to it's own file so it can be reused across components.
3034
@@ -87,27 +91,28 @@ export function mapSignal<T, V>(
8791 '[attr.aria-disabled]' : 'pattern.disabled()' ,
8892 '[attr.aria-orientation]' : 'pattern.orientation()' ,
8993 '[attr.aria-activedescendant]' : 'pattern.activedescendant()' ,
90- '(keydown)' : 'pattern. onKeydown($event)' ,
91- '(pointerdown)' : 'pattern. onPointerdown($event)' ,
94+ '(keydown)' : 'onKeydown($event)' ,
95+ '(pointerdown)' : 'onPointerdown($event)' ,
9296 '(focusin)' : 'onFocus()' ,
9397 } ,
98+ hostDirectives : [ CdkToolbarWidgetGroup ] ,
9499} )
95100export class CdkRadioGroup < V > {
96101 /** A reference to the radio group element. */
97102 private readonly _elementRef = inject ( ElementRef ) ;
98103
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+
99110 /** The CdkRadioButtons nested inside of the CdkRadioGroup. */
100111 private readonly _cdkRadioButtons = contentChildren ( CdkRadioButton , { descendants : true } ) ;
101112
102113 /** A signal wrapper for directionality. */
103114 protected textDirection = inject ( Directionality ) . valueSignal ;
104115
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-
111116 /** The RadioButton UIPatterns of the child CdkRadioButtons. */
112117 protected items = computed ( ( ) => this . _cdkRadioButtons ( ) . map ( radio => radio . pattern ) ) ;
113118
@@ -135,17 +140,36 @@ export class CdkRadioGroup<V> {
135140 reverse : values => ( values . length === 0 ? null : values [ 0 ] ) ,
136141 } ) ;
137142
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+
138156 /** The RadioGroup UIPattern. */
139- pattern : RadioGroupPattern < V > = new RadioGroupPattern < V > ( {
157+ readonly pattern : RadioGroupPattern < V > = new RadioGroupPattern < V > ( {
140158 ...this ,
141159 items : this . items ,
142160 value : this . _value ,
143161 activeItem : signal ( undefined ) ,
162+ orientation : this . _orientation ,
144163 textDirection : this . textDirection ,
145- toolbar : this . _toolbarPattern ,
164+ skipDisabled : this . _skipDisabled ,
146165 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 ,
149173 } ) ;
150174
151175 /** Whether the radio group has received focus yet. */
@@ -162,34 +186,34 @@ export class CdkRadioGroup<V> {
162186 } ) ;
163187
164188 afterRenderEffect ( ( ) => {
165- if ( ! this . _hasFocused ( ) && ! this . toolbar ) {
189+ if ( ! this . _hasFocused ( ) && ! this . _hasToolbar ( ) ) {
166190 this . pattern . setDefaultState ( ) ;
167191 }
168192 } ) ;
169193
170- // TODO: Refactor to be handled within list behavior
171194 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 ( ) ) ;
181197 }
182198 } ) ;
199+
200+ if ( this . _hasToolbar ( ) ) {
201+ this . _cdkToolbarWidgetGroup . handler . set ( i => this . pattern . toolbarExecute ( i ) ) ;
202+ }
183203 }
184204
185205 onFocus ( ) {
186206 this . _hasFocused . set ( true ) ;
187207 }
188208
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 ) ;
193217 }
194218}
195219
@@ -207,7 +231,7 @@ export class CdkRadioGroup<V> {
207231 '[id]' : 'pattern.id()' ,
208232 } ,
209233} )
210- export class CdkRadioButton < V > implements OnDestroy {
234+ export class CdkRadioButton < V > {
211235 /** A reference to the radio button element. */
212236 private readonly _elementRef = inject ( ElementRef ) ;
213237
@@ -218,13 +242,13 @@ export class CdkRadioButton<V> implements OnDestroy {
218242 private readonly _generatedId = inject ( _IdGenerator ) . getId ( 'cdk-radio-button-' ) ;
219243
220244 /** A unique identifier for the radio button. */
221- protected id = computed ( ( ) => this . _generatedId ) ;
245+ readonly id = computed ( ( ) => this . _generatedId ) ;
222246
223247 /** The value associated with the radio button. */
224248 readonly value = input . required < V > ( ) ;
225249
226250 /** The parent RadioGroup UIPattern. */
227- protected group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
251+ readonly group = computed ( ( ) => this . _cdkRadioGroup . pattern ) ;
228252
229253 /** A reference to the radio button element to be focused on navigation. */
230254 element = computed ( ( ) => this . _elementRef . nativeElement ) ;
@@ -240,10 +264,4 @@ export class CdkRadioButton<V> implements OnDestroy {
240264 group : this . group ,
241265 element : this . element ,
242266 } ) ;
243-
244- ngOnDestroy ( ) {
245- if ( this . _cdkRadioGroup . toolbar ) {
246- this . _cdkRadioGroup . toolbarButtonUnregister ( this ) ;
247- }
248- }
249267}
0 commit comments