Skip to content

Commit 6371abe

Browse files
committed
fix(cdk-experimental/ui-patterns) toolbar fix for focus change
1 parent a2081c7 commit 6371abe

File tree

4 files changed

+94
-52
lines changed

4 files changed

+94
-52
lines changed

src/cdk-experimental/toolbar/toolbar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class CdkToolbar<V> {
109109
/** The toolbar UIPattern. */
110110
pattern: ToolbarPattern<V> = new ToolbarPattern<V>({
111111
...this,
112-
activeIndex: signal(0),
112+
activeItem: signal(undefined),
113113
textDirection: this.textDirection,
114114
focusMode: this.focusMode,
115115
});

src/cdk-experimental/ui-patterns/radio-group/radio-button.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type GeneralWidget = {
2323
interface RadioGroupLike<V> {
2424
/** The list behavior for the radio group. */
2525
listBehavior: List<RadioButtonPattern<V>, V>;
26+
/** Whether the list is readonly */
27+
readonly: SignalLike<boolean>;
2628
}
2729

2830
/** Represents the required inputs for a radio button in a radio group. */

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,20 @@ export type RadioGroupInputs<V> = Omit<
2525
/** Parent toolbar of radio group */
2626
toolbar: SignalLike<ToolbarLike<V> | null>;
2727
};
28+
29+
type ToolbarWidget = {
30+
id: SignalLike<string>;
31+
index: SignalLike<number>;
32+
element: SignalLike<HTMLElement>;
33+
disabled: SignalLike<boolean>;
34+
searchTerm: SignalLike<any>;
35+
value: SignalLike<any>;
36+
};
37+
2838
interface ToolbarLike<V> {
29-
focusManager: ListFocus<RadioButtonPattern<V> | GeneralWidget>;
30-
navigation: ListNavigation<RadioButtonPattern<V> | GeneralWidget>;
39+
listBehavior: List<RadioButtonPattern<V> | ToolbarWidget, V>;
3140
orientation: SignalLike<'vertical' | 'horizontal'>;
41+
disabled: SignalLike<boolean>;
3242
}
3343
/** Controls the state of a radio group. */
3444
export class RadioGroupPattern<V> {
@@ -102,6 +112,11 @@ export class RadioGroupPattern<V> {
102112
pointerdown = computed(() => {
103113
const manager = new PointerEventManager();
104114

115+
// // If within a disabled toolbar relinquish pointer control
116+
// if (this.inputs.toolbar() && this.inputs.toolbar()!.disabled()) {
117+
// return manager;
118+
// }
119+
105120
if (this.readonly()) {
106121
// Navigate focus only in readonly mode.
107122
return manager.on(e => this.listBehavior.goto(this._getItem(e)!));
@@ -112,7 +127,8 @@ export class RadioGroupPattern<V> {
112127
});
113128

114129
constructor(readonly inputs: RadioGroupInputs<V>) {
115-
this.orientation = inputs.orientation;
130+
this.orientation =
131+
inputs.toolbar() !== null ? inputs.toolbar()!.orientation : inputs.orientation;
116132

117133
this.listBehavior = new List({
118134
...inputs,

src/cdk-experimental/ui-patterns/toolbar/toolbar.ts

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,42 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {computed} from '@angular/core';
9+
import {computed, signal} from '@angular/core';
1010
import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager';
11-
import {ListFocus, ListFocusInputs, ListFocusItem} from '../behaviors/list-focus/list-focus';
12-
import {
13-
ListNavigation,
14-
ListNavigationInputs,
15-
ListNavigationItem,
16-
} from '../behaviors/list-navigation/list-navigation';
11+
// import {ListFocus, ListFocusInputs, ListFocusItem} from '../behaviors/list-focus/list-focus';
12+
// import {
13+
// ListNavigation,
14+
// ListNavigationInputs,
15+
// ListNavigationItem,
16+
// } from '../behaviors/list-navigation/list-navigation';
1717
import {SignalLike} from '../behaviors/signal-like/signal-like';
1818

1919
import {RadioButtonPatternType, RadioButtonPattern} from '../radio-group/radio-button';
2020

21-
export type ToolbarInputs<V> = ListNavigationInputs<
22-
RadioButtonPatternType<V> | ToolbarWidgetPattern
23-
> &
24-
ListFocusInputs<RadioButtonPatternType<V> | ToolbarWidgetPattern> & {
25-
/** Whether the toolbar is disabled. */
26-
disabled: SignalLike<boolean>;
27-
};
21+
import {List, ListInputs, ListItem} from '../behaviors/list/list';
2822

29-
export class ToolbarPattern<V> {
30-
/** Controls navigation for the toolbar. */
31-
navigation: ListNavigation<RadioButtonPatternType<V> | ToolbarWidgetPattern>;
23+
// remove typeahead etc.
24+
export type ToolbarInputs<V> = Omit<
25+
ListInputs<ToolbarWidgetPattern | RadioButtonPattern<V>, V>,
26+
'multi' | 'typeaheadDelay' | 'value' | 'selectionMode'
27+
>;
28+
// ListInputs<ToolbarWidgetPattern | RadioButtonPattern<V>, V>;
3229

33-
/** Controls focus for the toolbar. */
34-
focusManager: ListFocus<RadioButtonPatternType<V> | ToolbarWidgetPattern>;
30+
export class ToolbarPattern<V> {
31+
/** The list behavior for the toolbar. */
32+
listBehavior: List<ToolbarWidgetPattern | RadioButtonPattern<V>, V>;
3533

3634
/** Whether the tablist is vertically or horizontally oriented. */
3735
readonly orientation: SignalLike<'vertical' | 'horizontal'>;
3836

3937
/** Whether the toolbar is disabled. */
40-
disabled = computed(() => this.inputs.disabled() || this.focusManager.isListDisabled());
38+
disabled = computed(() => this.inputs.disabled() || this.listBehavior.disabled());
4139

4240
/** The tabindex of the toolbar (if using activedescendant). */
43-
tabindex = computed(() => this.focusManager.getListTabindex());
41+
tabindex = computed(() => this.listBehavior.tabindex());
4442

4543
/** The id of the current active widget (if using activedescendant). */
46-
activedescendant = computed(() => this.focusManager.getActiveDescendant());
44+
activedescendant = computed(() => this.listBehavior.activedescendant());
4745

4846
/** The key used to navigate to the previous widget. */
4947
prevKey = computed(() => {
@@ -64,28 +62,44 @@ export class ToolbarPattern<V> {
6462
/** The keydown event manager for the toolbar. */
6563
keydown = computed(() => {
6664
const manager = new KeyboardEventManager();
65+
console.log(' all curent itmes', this.inputs.items());
6766

6867
return manager
6968
.on(' ', () => this.toolbarSelectOverride())
7069
.on('Enter', () => this.toolbarSelectOverride())
71-
.on(this.prevKey, () => this.navigation.prev())
72-
.on(this.nextKey, () => this.navigation.next())
73-
.on('Home', () => this.navigation.first())
74-
.on('End', () => this.navigation.last());
70+
.on(this.prevKey, () => this.listBehavior.prev())
71+
.on(this.nextKey, () => {
72+
console.log('next');
73+
this.next();
74+
})
75+
.on('Home', () => this.listBehavior.first())
76+
.on('End', () => this.listBehavior.last());
7577
});
78+
next() {
79+
const activeItem = this.inputs.activeItem();
80+
// if (activeItem instanceof RadioButtonPattern && activeItem.group()!!) {
81+
// console.log('let the group move itself');
82+
// activeItem.group()!!.listBehavior.next();
83+
// } else
84+
this.listBehavior.next();
85+
// find what is the next item
86+
// console.log('next item', this.listBehavior.nextItem());
87+
}
7688

7789
toolbarSelectOverride() {
78-
const activeItem = this.focusManager.activeItem();
90+
const activeItem = this.inputs.activeItem();
7991

8092
/** If the active item is a Radio Button, indicate to the group the selection */
8193
if (activeItem instanceof RadioButtonPattern) {
8294
const group = activeItem.group();
8395
if (group && !group.readonly()) {
84-
group.selection.selectOne();
96+
group.listBehavior.selectOne();
8597
}
98+
// todo fix
8699
} else {
87100
/** Item is a Toolbar Widget, manually select it */
88-
if (activeItem.element()) activeItem.element().click();
101+
if (activeItem && activeItem.element() && !activeItem.disabled())
102+
activeItem.element().click();
89103
}
90104
}
91105

@@ -100,8 +114,12 @@ export class ToolbarPattern<V> {
100114
/** Navigates to the widget associated with the given pointer event. */
101115
goto(event: PointerEvent) {
102116
const item = this._getItem(event);
117+
if (!item) return;
103118

104-
this.navigation.goto(item);
119+
if (item instanceof RadioButtonPattern) {
120+
// have the radio group handle the selection
121+
}
122+
this.listBehavior.goto(item);
105123
}
106124

107125
/** Handles keydown events for the toolbar. */
@@ -113,13 +131,14 @@ export class ToolbarPattern<V> {
113131

114132
/** Handles pointerdown events for the toolbar. */
115133
onPointerdown(event: PointerEvent) {
134+
console.log('this disabled', this.disabled());
116135
if (!this.disabled()) {
117136
this.pointerdown().handle(event);
118137
}
119138
}
120139

121140
/** Finds the Toolbar Widget associated with a pointer event target. */
122-
private _getItem(e: PointerEvent): RadioButtonPatternType<V> | ToolbarWidgetPattern | undefined {
141+
private _getItem(e: PointerEvent): RadioButtonPattern<V> | ToolbarWidgetPattern | undefined {
123142
if (!(e.target instanceof HTMLElement)) {
124143
return undefined;
125144
}
@@ -132,10 +151,12 @@ export class ToolbarPattern<V> {
132151
constructor(readonly inputs: ToolbarInputs<V>) {
133152
this.orientation = inputs.orientation;
134153

135-
this.focusManager = new ListFocus(inputs);
136-
this.navigation = new ListNavigation({
154+
this.listBehavior = new List({
137155
...inputs,
138-
focusManager: this.focusManager,
156+
multi: () => false,
157+
selectionMode: () => 'explicit',
158+
value: signal([] as any),
159+
typeaheadDelay: () => 0, // Toolbar widgets do not support typeahead.
139160
});
140161
}
141162

@@ -146,22 +167,23 @@ export class ToolbarPattern<V> {
146167
* Otherwise, sets the active index to the first focusable widget.
147168
*/
148169
setDefaultState() {
149-
let firstItem: RadioButtonPatternType<V> | ToolbarWidgetPattern | null = null;
170+
let firstItem: RadioButtonPattern<V> | ToolbarWidgetPattern | null = null;
150171

151172
for (const item of this.inputs.items()) {
152-
if (this.focusManager.isFocusable(item)) {
173+
if (this.listBehavior.isFocusable(item)) {
153174
if (!firstItem) {
154175
firstItem = item;
155176
}
156177
if (item instanceof RadioButtonPattern && item.selected()) {
157-
this.inputs.activeIndex.set(item.index());
178+
this.inputs.activeItem.set(item);
158179
return;
159180
}
160181
}
161182
}
162183

163184
if (firstItem) {
164-
this.inputs.activeIndex.set(firstItem.index());
185+
console.log('setting active item to', firstItem);
186+
this.inputs.activeItem.set(firstItem);
165187
}
166188
}
167189
/** Validates the state of the toolbar and returns a list of accessibility violations. */
@@ -183,12 +205,13 @@ export class ToolbarPattern<V> {
183205

184206
export type ToolbarWidget = {
185207
id: SignalLike<string>;
208+
index: SignalLike<number>;
186209
element: SignalLike<HTMLElement>;
187210
disabled: SignalLike<boolean>;
188211
};
189212

190213
/** Represents the required inputs for a toolbar widget in a toolbar. */
191-
export interface ToolbarWidgetInputs extends ListNavigationItem, ListFocusItem {
214+
export interface ToolbarWidgetInputs extends Omit<ListItem<any>, 'searchTerm' | 'value' | 'index'> {
192215
/** A reference to the parent toolbar. */
193216
parentToolbar: SignalLike<ToolbarPattern<null>>;
194217
}
@@ -205,18 +228,18 @@ export class ToolbarWidgetPattern {
205228
parentToolbar: SignalLike<ToolbarPattern<null> | undefined>;
206229

207230
/** The tabindex of the widgdet. */
208-
tabindex = computed(() => this.inputs.parentToolbar().focusManager.getItemTabindex(this));
231+
tabindex = computed(() => this.inputs.parentToolbar().listBehavior.getItemTabindex(this));
232+
233+
/** The text used by the typeahead search. */
234+
readonly searchTerm = () => ''; // Unused because toolbar does not support typeahead.
235+
236+
readonly value = () => '' as any; // Unused because toolbar does not support selection.
209237

210238
/** The position of the widget within the group. */
211-
index = computed(
212-
() =>
213-
this.parentToolbar()
214-
?.navigation.inputs.items()
215-
.findIndex(i => i.id() === this.id()) ?? -1,
216-
);
239+
index = computed(() => this.parentToolbar()?.inputs.items().indexOf(this) ?? -1);
217240

218-
/** Whether the widhet is currently the active one (focused). */
219-
active = computed(() => this.inputs.parentToolbar().focusManager.activeItem() === this);
241+
/** Whether the widget is currently the active one (focused). */
242+
active = computed(() => this.parentToolbar()?.inputs.activeItem() === this);
220243

221244
constructor(readonly inputs: ToolbarWidgetInputs) {
222245
this.id = inputs.id;
@@ -226,4 +249,5 @@ export class ToolbarWidgetPattern {
226249
}
227250
}
228251

252+
// can remove later
229253
export type ToolbarPatternType<V> = InstanceType<typeof ToolbarPattern<V>>;

0 commit comments

Comments
 (0)