Skip to content

Commit ca46fb9

Browse files
committed
refactor(cdk-experimental/ui-patterns): switch tabs to use the list behavior
1 parent faa75cc commit ca46fb9

File tree

2 files changed

+27
-95
lines changed

2 files changed

+27
-95
lines changed

src/cdk-experimental/ui-patterns/tabs/BUILD.bazel

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ ts_project(
1212
"//src/cdk-experimental/ui-patterns/behaviors/event-manager",
1313
"//src/cdk-experimental/ui-patterns/behaviors/expansion",
1414
"//src/cdk-experimental/ui-patterns/behaviors/label",
15-
"//src/cdk-experimental/ui-patterns/behaviors/list-focus",
16-
"//src/cdk-experimental/ui-patterns/behaviors/list-navigation",
17-
"//src/cdk-experimental/ui-patterns/behaviors/list-selection",
15+
"//src/cdk-experimental/ui-patterns/behaviors/list",
1816
"//src/cdk-experimental/ui-patterns/behaviors/signal-like",
1917
],
2018
)

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

Lines changed: 26 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,6 @@
88

99
import {computed} 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';
17-
import {
18-
ListSelection,
19-
ListSelectionInputs,
20-
ListSelectionItem,
21-
} from '../behaviors/list-selection/list-selection';
2211
import {
2312
ExpansionItem,
2413
ExpansionControl,
@@ -27,12 +16,11 @@ import {
2716
} from '../behaviors/expansion/expansion';
2817
import {SignalLike} from '../behaviors/signal-like/signal-like';
2918
import {LabelControl, LabelControlOptionalInputs} from '../behaviors/label/label';
19+
import {List, ListInputs, ListItem} from '../behaviors/list/list';
3020

3121
/** The required inputs to tabs. */
3222
export interface TabInputs
33-
extends ListNavigationItem,
34-
ListSelectionItem<string>,
35-
ListFocusItem,
23+
extends Omit<ListItem<string>, 'searchTerm'>,
3624
Omit<ExpansionItem, 'expansionId' | 'expandable'> {
3725
/** The parent tablist that controls the tab. */
3826
tablist: SignalLike<TabListPattern>;
@@ -58,6 +46,9 @@ export class TabPattern {
5846
/** The html element that should receive focus. */
5947
readonly element: SignalLike<HTMLElement>;
6048

49+
/** The text used by the typeahead search. */
50+
readonly searchTerm = () => ''; // Unused because tabs do not support typeahead.
51+
6152
/** Whether this tab has expandable content. */
6253
readonly expandable = computed(() => this.expansion.expandable());
6354

@@ -68,15 +59,13 @@ export class TabPattern {
6859
readonly expanded = computed(() => this.expansion.isExpanded());
6960

7061
/** Whether the tab is active. */
71-
readonly active = computed(() => this.inputs.tablist().focusManager.activeItem() === this);
62+
readonly active = computed(() => this.inputs.tablist().listBehavior.activeItem() === this);
7263

7364
/** Whether the tab is selected. */
74-
readonly selected = computed(
75-
() => !!this.inputs.tablist().selection.inputs.value().includes(this.value()),
76-
);
65+
readonly selected = computed(() => !!this.inputs.tablist().inputs.value().includes(this.value()));
7766

7867
/** The tabindex of the tab. */
79-
readonly tabindex = computed(() => this.inputs.tablist().focusManager.getItemTabindex(this));
68+
readonly tabindex = computed(() => this.inputs.tablist().listBehavior.getItemTabindex(this));
8069

8170
/** The id of the tabpanel associated with the tab. */
8271
readonly controls = computed(() => this.inputs.tabpanel()?.id());
@@ -136,27 +125,14 @@ export class TabPanelPattern {
136125
}
137126
}
138127

139-
/** The selection operations that the tablist can perform. */
140-
interface SelectOptions {
141-
select?: boolean;
142-
}
143-
144128
/** The required inputs for the tablist. */
145-
export type TabListInputs = ListNavigationInputs<TabPattern> &
146-
Omit<ListSelectionInputs<TabPattern, string>, 'multi'> &
147-
ListFocusInputs<TabPattern> &
129+
export type TabListInputs = Omit<ListInputs<TabPattern, string>, 'multi' | 'typeaheadDelay'> &
148130
Omit<ListExpansionInputs, 'multiExpandable' | 'expandedIds' | 'items'>;
149131

150132
/** Controls the state of a tablist. */
151133
export class TabListPattern {
152-
/** Controls navigation for the tablist. */
153-
readonly navigation: ListNavigation<TabPattern>;
154-
155-
/** Controls selection for the tablist. */
156-
readonly selection: ListSelection<TabPattern, string>;
157-
158-
/** Controls focus for the tablist. */
159-
readonly focusManager: ListFocus<TabPattern>;
134+
/** The list behavior for the tablist. */
135+
readonly listBehavior: List<TabPattern, string>;
160136

161137
/** Controls expansion for the tablist. */
162138
readonly expansionManager: ListExpansion;
@@ -168,10 +144,10 @@ export class TabListPattern {
168144
readonly disabled: SignalLike<boolean>;
169145

170146
/** The tabindex of the tablist. */
171-
readonly tabindex = computed(() => this.focusManager.getListTabindex());
147+
readonly tabindex = computed(() => this.listBehavior.tabindex());
172148

173149
/** The id of the current active tab. */
174-
readonly activedescendant = computed(() => this.focusManager.getActiveDescendant());
150+
readonly activedescendant = computed(() => this.listBehavior.activedescendant());
175151

176152
/** Whether selection should follow focus. */
177153
readonly followFocus = computed(() => this.inputs.selectionMode() === 'follow');
@@ -195,30 +171,29 @@ export class TabListPattern {
195171
/** The keydown event manager for the tablist. */
196172
readonly keydown = computed(() => {
197173
return new KeyboardEventManager()
198-
.on(this.prevKey, () => this.prev({select: this.followFocus()}))
199-
.on(this.nextKey, () => this.next({select: this.followFocus()}))
200-
.on('Home', () => this.first({select: this.followFocus()}))
201-
.on('End', () => this.last({select: this.followFocus()}))
202-
.on(' ', () => this._select({select: true}))
203-
.on('Enter', () => this._select({select: true}));
174+
.on(this.prevKey, () => this.listBehavior.prev({select: this.followFocus()}))
175+
.on(this.nextKey, () => this.listBehavior.next({select: this.followFocus()}))
176+
.on('Home', () => this.listBehavior.first({select: this.followFocus()}))
177+
.on('End', () => this.listBehavior.last({select: this.followFocus()}))
178+
.on(' ', () => this.listBehavior.select())
179+
.on('Enter', () => this.listBehavior.select());
204180
});
205181

206182
/** The pointerdown event manager for the tablist. */
207183
readonly pointerdown = computed(() => {
208-
return new PointerEventManager().on(e => this.goto(e, {select: true}));
184+
return new PointerEventManager().on(e =>
185+
this.listBehavior.goto(this._getItem(e)!, {select: true}),
186+
);
209187
});
210188

211189
constructor(readonly inputs: TabListInputs) {
212190
this.disabled = inputs.disabled;
213191
this.orientation = inputs.orientation;
214192

215-
this.focusManager = new ListFocus(inputs);
216-
this.navigation = new ListNavigation({...inputs, focusManager: this.focusManager});
217-
218-
this.selection = new ListSelection({
193+
this.listBehavior = new List({
219194
...inputs,
220195
multi: () => false,
221-
focusManager: this.focusManager,
196+
typeaheadDelay: () => 0, // Tabs do not support typeahead.
222197
});
223198

224199
this.expansionManager = new ListExpansion({
@@ -240,7 +215,7 @@ export class TabListPattern {
240215
let firstItemIndex: number | undefined;
241216

242217
for (const [index, item] of this.inputs.items().entries()) {
243-
if (!this.focusManager.isFocusable(item)) continue;
218+
if (!this.listBehavior.isFocusable(item)) continue;
244219

245220
if (firstItemIndex === undefined) {
246221
firstItemIndex = index;
@@ -270,48 +245,7 @@ export class TabListPattern {
270245
}
271246
}
272247

273-
/** Navigates to the first option in the tablist. */
274-
first(opts?: SelectOptions) {
275-
this.navigation.first();
276-
this._select(opts);
277-
}
278-
279-
/** Navigates to the last option in the tablist. */
280-
last(opts?: SelectOptions) {
281-
this.navigation.last();
282-
this._select(opts);
283-
}
284-
285-
/** Navigates to the next option in the tablist. */
286-
next(opts?: SelectOptions) {
287-
this.navigation.next();
288-
this._select(opts);
289-
}
290-
291-
/** Navigates to the previous option in the tablist. */
292-
prev(opts?: SelectOptions) {
293-
this.navigation.prev();
294-
this._select(opts);
295-
}
296-
297-
/** Navigates to the given item in the tablist. */
298-
goto(event: PointerEvent, opts?: SelectOptions) {
299-
const item = this._getItem(event);
300-
301-
if (item) {
302-
this.navigation.goto(item);
303-
this._select(opts);
304-
}
305-
}
306-
307-
/** Handles updating selection for the tablist. */
308-
private _select(opts?: SelectOptions) {
309-
if (opts?.select) {
310-
this.selection.selectOne();
311-
this.expansionManager.open(this.focusManager.activeItem());
312-
}
313-
}
314-
248+
/** Returns the tab item associated with the given pointer event. */
315249
private _getItem(e: PointerEvent) {
316250
if (!(e.target instanceof HTMLElement)) {
317251
return;

0 commit comments

Comments
 (0)