Skip to content

Commit 88f775a

Browse files
committed
Enforced strict tab/tabpanel matching by value. Capitalized
list/panel/content.
1 parent 91e4d02 commit 88f775a

File tree

7 files changed

+88
-82
lines changed

7 files changed

+88
-82
lines changed

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

+1-1
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 {CdkTabs, CdkTablist, CdkTab, CdkTabpanel, CdkTabcontent} from './tabs';
9+
export {CdkTabs, CdkTabList, CdkTab, CdkTabPanel, CdkTabContent} from './tabs';

src/cdk-experimental/tabs/tabs.ts

+47-47
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,28 @@ import {
2121
import {Directionality} from '@angular/cdk/bidi';
2222
import {DeferredContent, DeferredContentAware} from '@angular/cdk-experimental/deferred-content';
2323
import {TabPattern} from '@angular/cdk-experimental/ui-patterns/tabs/tab';
24-
import {TablistPattern} from '@angular/cdk-experimental/ui-patterns/tabs/tablist';
25-
import {TabpanelPattern} from '@angular/cdk-experimental/ui-patterns/tabs/tabpanel';
24+
import {TabListPattern} from '@angular/cdk-experimental/ui-patterns/tabs/tablist';
25+
import {TabPanelPattern} from '@angular/cdk-experimental/ui-patterns/tabs/tabpanel';
2626
import {toSignal} from '@angular/core/rxjs-interop';
2727
import {_IdGenerator} from '@angular/cdk/a11y';
2828

2929
/**
3030
* A Tabs container.
3131
*
3232
* Represents a set of layered sections of content. The CdkTabs is a container meant to be used with
33-
* CdkTablist, CdkTab, and CdkTabpanel as follows:
33+
* CdkTabList, CdkTab, and CdkTabPanel as follows:
3434
*
3535
* ```html
3636
* <div cdkTabs>
37-
* <ul cdkTablist>
37+
* <ul cdkTabList>
3838
* <li cdkTab>Tab 1</li>
3939
* <li cdkTab>Tab 2</li>
4040
* <li cdkTab>Tab 3</li>
4141
* </ul>
4242
*
43-
* <div cdkTabpanel>Tab content 1</div>
44-
* <div cdkTabpanel>Tab content 2</div>
45-
* <div cdkTabpanel>Tab content 3</div>
43+
* <div cdkTabPanel>Tab content 1</div>
44+
* <div cdkTabPanel>Tab content 2</div>
45+
* <div cdkTabPanel>Tab content 3</div>
4646
* </div>
4747
* ```
4848
*/
@@ -57,24 +57,24 @@ export class CdkTabs {
5757
/** The CdkTabs nested inside of the container. */
5858
private readonly _cdkTabs = contentChildren(CdkTab, {descendants: true});
5959

60-
/** The CdkTabpanels nested inside of the container. */
61-
private readonly _cdkTabpanels = contentChildren(CdkTabpanel, {descendants: true});
60+
/** The CdkTabPanels nested inside of the container. */
61+
private readonly _cdkTabPanels = contentChildren(CdkTabPanel, {descendants: true});
6262

6363
/** The Tab UIPattern of the child Tabs. */
6464
tabs = computed(() => this._cdkTabs().map(tab => tab.pattern));
6565

66-
/** The Tabpanel UIPattern of the child Tabpanels. */
67-
tabpanels = computed(() => this._cdkTabpanels().map(tabpanel => tabpanel.pattern));
66+
/** The TabPanel UIPattern of the child TabPanels. */
67+
tabpanels = computed(() => this._cdkTabPanels().map(tabpanel => tabpanel.pattern));
6868
}
6969

7070
/**
71-
* A Tablist container.
71+
* A TabList container.
7272
*
7373
* Controls a list of CdkTab(s).
7474
*/
7575
@Directive({
76-
selector: '[cdkTablist]',
77-
exportAs: 'cdkTablist',
76+
selector: '[cdkTabList]',
77+
exportAs: 'cdkTabList',
7878
host: {
7979
'role': 'tablist',
8080
'class': 'cdk-tablist',
@@ -86,11 +86,11 @@ export class CdkTabs {
8686
'(pointerdown)': 'pattern.onPointerdown($event)',
8787
},
8888
})
89-
export class CdkTablist {
89+
export class CdkTabList {
9090
/** The directionality (LTR / RTL) context for the application (or a subtree of it). */
9191
private readonly _directionality = inject(Directionality);
9292

93-
/** The CdkTabs nested inside of the CdkTablist. */
93+
/** The CdkTabs nested inside of the CdkTabList. */
9494
private readonly _cdkTabs = contentChildren(CdkTab);
9595

9696
/** A signal wrapper for directionality. */
@@ -122,16 +122,16 @@ export class CdkTablist {
122122
/** The current index that has been navigated to. */
123123
activeIndex = model<number>(0);
124124

125-
/** The Tablist UIPattern. */
126-
pattern: TablistPattern = new TablistPattern({
125+
/** The TabList UIPattern. */
126+
pattern: TabListPattern = new TabListPattern({
127127
...this,
128128
items: this.tabs,
129129
textDirection: this.textDirection,
130130
value: signal<string[]>([]),
131131
});
132132
}
133133

134-
/** A selectable tab in a tablist. */
134+
/** A selectable tab in a TabList. */
135135
@Directive({
136136
selector: '[cdkTab]',
137137
exportAs: 'cdkTab',
@@ -152,46 +152,48 @@ export class CdkTab {
152152
/** The parent CdkTabs. */
153153
private readonly _cdkTabs = inject(CdkTabs);
154154

155-
/** The parent CdkTablist. */
156-
private readonly _cdkTablist = inject(CdkTablist);
155+
/** The parent CdkTabList. */
156+
private readonly _cdkTabList = inject(CdkTabList);
157157

158-
/** A unique identifier for the tab. */
158+
/** A global unique identifier for the tab. */
159159
private readonly _id = inject(_IdGenerator).getId('cdk-tab-');
160160

161-
/** The position of the tab in the list. */
162-
protected index = computed(() => this._cdkTabs.tabs().findIndex(tab => tab.id() === this._id));
161+
/** The parent TabList UIPattern. */
162+
protected tablist = computed(() => this._cdkTabList.pattern);
163163

164-
/** The parent Tablist UIPattern. */
165-
protected tablist = computed(() => this._cdkTablist.pattern);
166-
167-
/** The Tabpanel UIPattern associated with the tab */
168-
protected tabpanel = computed(() => this._cdkTabs.tabpanels()[this.index()]);
164+
/** The TabPanel UIPattern associated with the tab */
165+
protected tabpanel = computed(() =>
166+
this._cdkTabs.tabpanels().find(tabpanel => tabpanel.value() === this.value()),
167+
);
169168

170169
/** Whether a tab is disabled. */
171170
disabled = input(false, {transform: booleanAttribute});
172171

172+
/** A local unique identifier for the tab. */
173+
value = input.required<string>();
174+
173175
/** The Tab UIPattern. */
174176
pattern: TabPattern = new TabPattern({
175177
...this,
176178
id: () => this._id,
177-
value: () => this._id,
178179
element: () => this._elementRef.nativeElement,
179180
tablist: this.tablist,
180181
tabpanel: this.tabpanel,
182+
value: this.value,
181183
});
182184
}
183185

184186
/**
185-
* A Tabpanel container for the resources of layered content associated with a tab.
187+
* A TabPanel container for the resources of layered content associated with a tab.
186188
*
187189
* If a tabpanel is hidden due to its corresponding tab is not activated, the `inert` attribute
188190
* will be applied to the tabpanel element to remove it from the accessibility tree and stop
189191
* all the keyboard and pointer interactions. Note that this does not visually hide the tabpenl
190192
* and a proper styling is required.
191193
*/
192194
@Directive({
193-
selector: '[cdkTabpanel]',
194-
exportAs: 'cdkTabpanel',
195+
selector: '[cdkTabPanel]',
196+
exportAs: 'cdkTabPanel',
195197
host: {
196198
'role': 'tabpanel',
197199
'tabindex': '0',
@@ -206,26 +208,24 @@ export class CdkTab {
206208
},
207209
],
208210
})
209-
export class CdkTabpanel {
211+
export class CdkTabPanel {
210212
/** The DeferredContentAware host directive. */
211213
private readonly _deferredContentAware = inject(DeferredContentAware);
212214

213215
/** The parent CdkTabs. */
214216
private readonly _cdkTabs = inject(CdkTabs);
215217

216-
/** A unique identifier for the tab. */
218+
/** A global unique identifier for the tab. */
217219
private readonly _id = inject(_IdGenerator).getId('cdk-tabpanel-');
218220

219-
/** The position of the tabpanel in the tabs. */
220-
protected index = computed(() =>
221-
this._cdkTabs.tabpanels().findIndex(tabpanel => tabpanel.id() === this._id),
222-
);
223-
224221
/** The Tab UIPattern associated with the tabpanel */
225-
protected tab = computed(() => this._cdkTabs.tabs()[this.index()]);
222+
protected tab = computed(() => this._cdkTabs.tabs().find(tab => tab.value() === this.value()));
223+
224+
/** A local unique identifier for the tabpanel. */
225+
value = input.required<string>();
226226

227-
/** The Tabpanel UIPattern. */
228-
pattern: TabpanelPattern = new TabpanelPattern({
227+
/** The TabPanel UIPattern. */
228+
pattern: TabPanelPattern = new TabPanelPattern({
229229
...this,
230230
id: () => this._id,
231231
tab: this.tab,
@@ -237,11 +237,11 @@ export class CdkTabpanel {
237237
}
238238

239239
/**
240-
* A Tabcontent container for the lazy-loaded content.
240+
* A TabContent container for the lazy-loaded content.
241241
*/
242242
@Directive({
243-
selector: 'ng-template[cdkTabcontent]',
244-
exportAs: 'cdTabcontent',
243+
selector: 'ng-template[cdkTabContent]',
244+
exportAs: 'cdTabContent',
245245
hostDirectives: [DeferredContent],
246246
})
247-
export class CdkTabcontent {}
247+
export class CdkTabContent {}

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

+11-10
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,37 @@ import {SignalLike} from '../behaviors/signal-like/signal-like';
1111
import {ListSelectionItem} from '../behaviors/list-selection/list-selection';
1212
import {ListNavigationItem} from '../behaviors/list-navigation/list-navigation';
1313
import {ListFocusItem} from '../behaviors/list-focus/list-focus';
14-
import {TabpanelPattern} from './tabpanel';
15-
import {TablistPattern} from './tablist';
14+
import {TabPanelPattern} from './tabpanel';
15+
import {TabListPattern} from './tablist';
1616

1717
/** The required inputs to tabs. */
1818
export interface TabInputs extends ListNavigationItem, ListSelectionItem<string>, ListFocusItem {
19-
tablist: SignalLike<TablistPattern>;
20-
tabpanel: SignalLike<TabpanelPattern>;
19+
tablist: SignalLike<TabListPattern>;
20+
tabpanel: SignalLike<TabPanelPattern | undefined>;
2121
}
2222

2323
/** A tab in a tablist. */
2424
export class TabPattern {
25-
/** A unique identifier for the tab. */
25+
/** A global unique identifier for the tab. */
2626
id: SignalLike<string>;
2727

28-
/** The value of the tab. */
29-
value = () => this.id();
28+
/** A local unique identifier for the tab. */
29+
value: SignalLike<string>;
3030

3131
/** Whether the tab is selected. */
3232
selected = computed(() => this.tablist().selection.inputs.value().includes(this.value()));
3333

3434
/** A Tabpanel Id controlled by the tab. */
35-
controls = computed(() => this.tabpanel().id());
35+
controls = computed(() => this.tabpanel()?.id());
3636

3737
/** Whether the tab is disabled. */
3838
disabled: SignalLike<boolean>;
3939

4040
/** A reference to the parent tablist. */
41-
tablist: SignalLike<TablistPattern>;
41+
tablist: SignalLike<TabListPattern>;
4242

4343
/** A reference to the corresponding tabpanel. */
44-
tabpanel: SignalLike<TabpanelPattern>;
44+
tabpanel: SignalLike<TabPanelPattern | undefined>;
4545

4646
/** The tabindex of the tab. */
4747
tabindex = computed(() => this.tablist().focusManager.getItemTabindex(this));
@@ -51,6 +51,7 @@ export class TabPattern {
5151

5252
constructor(inputs: TabInputs) {
5353
this.id = inputs.id;
54+
this.value = inputs.value;
5455
this.tablist = inputs.tablist;
5556
this.tabpanel = inputs.tabpanel;
5657
this.element = inputs.element;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export type TablistInputs = ListNavigationInputs<TabPattern> &
3131
};
3232

3333
/** Controls the state of a tablist. */
34-
export class TablistPattern {
34+
export class TabListPattern {
3535
/** Controls navigation for the tablist. */
3636
navigation: ListNavigation<TabPattern>;
3737

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

+12-7
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,29 @@ import {SignalLike} from '../behaviors/signal-like/signal-like';
1111
import {TabPattern} from './tab';
1212

1313
/** The required inputs for the tabpanel. */
14-
export interface TabpanelInputs {
14+
export interface TabPanelInputs {
1515
id: SignalLike<string>;
16-
tab: SignalLike<TabPattern>;
16+
tab: SignalLike<TabPattern | undefined>;
17+
value: SignalLike<string>;
1718
}
1819

1920
/** A tabpanel associated with a tab. */
20-
export class TabpanelPattern {
21-
/** A unique identifier for the tabpanel. */
21+
export class TabPanelPattern {
22+
/** A global unique identifier for the tabpanel. */
2223
id: SignalLike<string>;
2324

25+
/** A local unique identifier for the tabpanel. */
26+
value: SignalLike<string>;
27+
2428
/** A reference to the corresponding tab. */
25-
tab: SignalLike<TabPattern>;
29+
tab: SignalLike<TabPattern | undefined>;
2630

2731
/** Whether the tabpanel is hidden. */
28-
hidden = computed(() => !this.tab().selected());
32+
hidden = computed(() => !this.tab()?.selected());
2933

30-
constructor(inputs: TabpanelInputs) {
34+
constructor(inputs: TabPanelInputs) {
3135
this.id = inputs.id;
36+
this.value = inputs.value;
3237
this.tab = inputs.tab;
3338
}
3439
}

src/components-examples/cdk-experimental/tabs/cdk-tabs/cdk-tabs-example.html

+10-10
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<!-- #docregion tabs -->
3232
<div cdkTabs class="example-tabs">
3333
<ul
34-
cdkTablist
34+
cdkTabList
3535
class="example-tablist"
3636
[wrap]="wrap.value"
3737
[disabled]="disabled.value"
@@ -40,19 +40,19 @@
4040
[focusMode]="focusMode"
4141
[selectionMode]="selectionMode"
4242
>
43-
<li cdkTab class="example-tab">tab 1</li>
44-
<li cdkTab class="example-tab">tab 2</li>
45-
<li cdkTab class="example-tab">tab 3</li>
43+
<li cdkTab value="tab-1" class="example-tab">tab 1</li>
44+
<li cdkTab value="tab-2" class="example-tab">tab 2</li>
45+
<li cdkTab value="tab-3" class="example-tab">tab 3</li>
4646
</ul>
4747

48-
<div cdkTabpanel class="example-tabpanel">
49-
<ng-template cdkTabcontent>Tabpanel 1</ng-template>
48+
<div cdkTabPanel value="tab-1" class="example-tabpanel">
49+
<ng-template cdkTabContent>Tabpanel 1</ng-template>
5050
</div>
51-
<div cdkTabpanel class="example-tabpanel">
52-
<ng-template cdkTabcontent>Tabpanel 2</ng-template>
51+
<div cdkTabPanel value="tab-2" class="example-tabpanel">
52+
<ng-template cdkTabContent>Tabpanel 2</ng-template>
5353
</div>
54-
<div cdkTabpanel class="example-tabpanel">
55-
<ng-template cdkTabcontent>Tabpanel 3</ng-template>
54+
<div cdkTabPanel value="tab-3" class="example-tabpanel">
55+
<ng-template cdkTabContent>Tabpanel 3</ng-template>
5656
</div>
5757
</div>
5858
<!-- #enddocregion tabs -->

src/components-examples/cdk-experimental/tabs/cdk-tabs/cdk-tabs-example.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {Component} from '@angular/core';
22
import {MatCheckboxModule} from '@angular/material/checkbox';
33
import {
44
CdkTabs,
5-
CdkTablist,
5+
CdkTabList,
66
CdkTab,
7-
CdkTabpanel,
8-
CdkTabcontent,
7+
CdkTabPanel,
8+
CdkTabContent,
99
} from '@angular/cdk-experimental/tabs';
1010
import {MatFormFieldModule} from '@angular/material/form-field';
1111
import {MatSelectModule} from '@angular/material/select';
@@ -19,10 +19,10 @@ import {FormControl, ReactiveFormsModule} from '@angular/forms';
1919
styleUrl: 'cdk-tabs-example.css',
2020
imports: [
2121
CdkTabs,
22-
CdkTablist,
22+
CdkTabList,
2323
CdkTab,
24-
CdkTabpanel,
25-
CdkTabcontent,
24+
CdkTabPanel,
25+
CdkTabContent,
2626
MatCheckboxModule,
2727
MatFormFieldModule,
2828
MatSelectModule,

0 commit comments

Comments
 (0)