Skip to content

Commit 2bc2c36

Browse files
committed
refactor(cdk-experimental/ui-patterns): separate interaction from toolbar and radio group
1 parent 9081e23 commit 2bc2c36

File tree

13 files changed

+784
-399
lines changed

13 files changed

+784
-399
lines changed

src/cdk-experimental/radio-group/radio-group.ts

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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';
2529
import {Directionality} from '@angular/cdk/bidi';
2630
import {_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
})
95100
export 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
}

src/cdk-experimental/toolbar/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
export {CdkToolbar, CdkToolbarWidget} from './toolbar';
9+
export {CdkToolbar, CdkToolbarWidget, CdkToolbarWidgetGroup} from './toolbar';

0 commit comments

Comments
 (0)