diff --git a/goldens/aria/private/index.api.md b/goldens/aria/private/index.api.md index b6c1d72b8fa7..e21af5b10be9 100644 --- a/goldens/aria/private/index.api.md +++ b/goldens/aria/private/index.api.md @@ -660,14 +660,14 @@ export type SignalLike = () => T; export function sortDirectives(a: HasElement, b: HasElement): 1 | -1; // @public -export interface TabInputs extends Omit, Omit { - tablist: SignalLike; - tabpanel: SignalLike; - value: SignalLike; +export interface TabInputs extends Omit, Omit { + tabList: SignalLike; + tabPanel: SignalLike; } // @public export interface TabListInputs extends Omit, 'multi'>, Omit { + selectedTab: WritableSignalLike; selectionMode: SignalLike<'follow' | 'explicit'>; } @@ -690,7 +690,6 @@ export class TabListPattern { onClick(event: PointerEvent): void; onFocusIn(): void; onKeydown(event: KeyboardEvent): void; - open(value: string): boolean; open(tab?: TabPattern): boolean; readonly orientation: SignalLike<'vertical' | 'horizontal'>; readonly prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; @@ -703,8 +702,7 @@ export class TabListPattern { // @public export interface TabPanelInputs extends LabelControlOptionalInputs { id: SignalLike; - tab: SignalLike; - value: SignalLike; + readonly tab: SignalLike; } // @public @@ -717,7 +715,6 @@ export class TabPanelPattern { readonly labelledBy: SignalLike; readonly labelManager: LabelControl; readonly tabIndex: SignalLike<-1 | 0>; - readonly value: SignalLike; } // @public @@ -728,15 +725,14 @@ export class TabPattern { readonly disabled: SignalLike; readonly element: SignalLike; readonly expandable: SignalLike; + // (undocumented) readonly expanded: WritableSignalLike; readonly id: SignalLike; - readonly index: SignalLike; // (undocumented) readonly inputs: TabInputs; open(): boolean; readonly selected: SignalLike; readonly tabIndex: SignalLike<0 | -1>; - readonly value: SignalLike; } // @public diff --git a/goldens/aria/tabs/index.api.md b/goldens/aria/tabs/index.api.md index 0430af759711..227c79de184b 100644 --- a/goldens/aria/tabs/index.api.md +++ b/goldens/aria/tabs/index.api.md @@ -8,6 +8,7 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; +import { WritableSignal } from '@angular/core'; // @public export class Tab implements HasElement, OnInit, OnDestroy { @@ -20,11 +21,12 @@ export class Tab implements HasElement, OnInit, OnDestroy { // (undocumented) ngOnInit(): void; open(): void; + readonly panel: _angular_core.Signal; + readonly panelRef: _angular_core.InputSignal; readonly _pattern: TabPattern; readonly selected: _angular_core.Signal; - readonly value: _angular_core.InputSignal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } @@ -42,23 +44,25 @@ export class TabList implements OnInit, OnDestroy { constructor(); readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; + // (undocumented) + findTab(panelId?: string): Tab | undefined; readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">; // (undocumented) ngOnDestroy(): void; // (undocumented) ngOnInit(): void; - open(value: string): boolean; + open(panelId: string): boolean; readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">; readonly _pattern: TabListPattern; // (undocumented) - _register(child: Tab): void; + _registerTab(child: Tab): void; readonly selectedTab: _angular_core.ModelSignal; readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">; readonly softDisabled: _angular_core.InputSignalWithTransform; - readonly _tabPatterns: _angular_core.Signal; - readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; + readonly _sortedTabs: _angular_core.Signal; + readonly textDirection: WritableSignal<_angular_cdk_bidi.Direction>; // (undocumented) - _unregister(child: Tab): void; + _unregisterTab(child: Tab): void; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) static ɵdir: _angular_core.ɵɵDirectiveDeclaration; @@ -76,23 +80,28 @@ export class TabPanel implements OnInit, OnDestroy { // (undocumented) ngOnInit(): void; readonly _pattern: TabPanelPattern; - readonly value: _angular_core.InputSignal; + readonly _tabPattern: WritableSignal; readonly visible: _angular_core.Signal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } // @public export class Tabs { + constructor(); readonly element: HTMLElement; // (undocumented) - _register(child: TabList | TabPanel): void; - readonly _tabPatterns: _angular_core.Signal; - readonly _unorderedTabpanelPatterns: _angular_core.Signal; + findTabPanel(id?: string): TabPanel | undefined; + // (undocumented) + _registerList(list: TabList): void; + // (undocumented) + _registerPanel(panel: TabPanel): void; + // (undocumented) + _unregisterList(list: TabList): void; // (undocumented) - _unregister(child: TabList | TabPanel): void; + _unregisterPanel(panel: TabPanel): void; // (undocumented) static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) diff --git a/src/aria/private/tabs/tabs.spec.ts b/src/aria/private/tabs/tabs.spec.ts index 6712ca417098..be888aa391ba 100644 --- a/src/aria/private/tabs/tabs.spec.ts +++ b/src/aria/private/tabs/tabs.spec.ts @@ -65,37 +65,32 @@ describe('Tabs Pattern', () => { softDisabled: signal(true), items: signal([]), element: signal(document.createElement('div')), + selectedTab: signal(undefined), }; tabListPattern = new TabListPattern(tabListInputs); // Initiate a list of TabPatterns. tabInputs = [ { - tablist: signal(tabListPattern), - tabpanel: signal(undefined), + tabList: signal(tabListPattern), + tabPanel: signal(undefined), id: signal('tab-1-id'), element: signal(createTabElement()), disabled: signal(false), - value: signal('tab-1'), - expanded: signal(false), }, { - tablist: signal(tabListPattern), - tabpanel: signal(undefined), + tabList: signal(tabListPattern), + tabPanel: signal(undefined), id: signal('tab-2-id'), element: signal(createTabElement()), disabled: signal(false), - value: signal('tab-2'), - expanded: signal(false), }, { - tablist: signal(tabListPattern), - tabpanel: signal(undefined), + tabList: signal(tabListPattern), + tabPanel: signal(undefined), id: signal('tab-3-id'), element: signal(createTabElement()), disabled: signal(false), - value: signal('tab-3'), - expanded: signal(false), }, ]; tabPatterns = [ @@ -109,17 +104,14 @@ describe('Tabs Pattern', () => { { id: signal('tabpanel-1-id'), tab: signal(undefined), - value: signal('tab-1'), }, { id: signal('tabpanel-2-id'), tab: signal(undefined), - value: signal('tab-2'), }, { id: signal('tabpanel-3-id'), tab: signal(undefined), - value: signal('tab-3'), }, ]; tabPanelPatterns = [ @@ -129,9 +121,9 @@ describe('Tabs Pattern', () => { ]; // Binding between tabs and tabpanels. - tabInputs[0].tabpanel.set(tabPanelPatterns[0]); - tabInputs[1].tabpanel.set(tabPanelPatterns[1]); - tabInputs[2].tabpanel.set(tabPanelPatterns[2]); + tabInputs[0].tabPanel.set(tabPanelPatterns[0]); + tabInputs[1].tabPanel.set(tabPanelPatterns[1]); + tabInputs[2].tabPanel.set(tabPanelPatterns[2]); tabPanelInputs[0].tab.set(tabPatterns[0]); tabPanelInputs[1].tab.set(tabPatterns[1]); tabPanelInputs[2].tab.set(tabPatterns[2]); @@ -143,8 +135,8 @@ describe('Tabs Pattern', () => { describe('#open', () => { it('should open a tab with value', () => { expect(tabListPattern.selectedTab()).toBeUndefined(); - tabListPattern.open('tab-1'); - expect(tabListPattern.selectedTab()!.value()).toBe('tab-1'); + tabListPattern.open(tabPatterns[0]); + expect(tabListPattern.selectedTab()!).toBe(tabPatterns[0]); }); it('should open a tab with tab pattern instance', () => { diff --git a/src/aria/private/tabs/tabs.ts b/src/aria/private/tabs/tabs.ts index 08702392b664..7346f2dc3af5 100644 --- a/src/aria/private/tabs/tabs.ts +++ b/src/aria/private/tabs/tabs.ts @@ -7,47 +7,39 @@ */ import {KeyboardEventManager, ClickEventManager} from '../behaviors/event-manager'; -import {ExpansionItem, ListExpansionInputs, ListExpansion} from '../behaviors/expansion/expansion'; +import {ExpansionItem, ListExpansion, ListExpansionInputs} from '../behaviors/expansion/expansion'; import { SignalLike, + WritableSignalLike, computed, + linkedSignal, signal, - WritableSignalLike, } from '../behaviors/signal-like/signal-like'; import {LabelControl, LabelControlOptionalInputs} from '../behaviors/label/label'; import {ListFocus} from '../behaviors/list-focus/list-focus'; import { - ListNavigationItem, ListNavigation, ListNavigationInputs, + ListNavigationItem, } from '../behaviors/list-navigation/list-navigation'; /** The required inputs to tabs. */ export interface TabInputs - extends Omit, Omit { + extends Omit, Omit { /** The parent tablist that controls the tab. */ - tablist: SignalLike; + tabList: SignalLike; /** The remote tabpanel controlled by the tab. */ - tabpanel: SignalLike; - - /** The remote tabpanel unique identifier. */ - value: SignalLike; + tabPanel: SignalLike; } /** A tab in a tablist. */ export class TabPattern { /** A global unique identifier for the tab. */ - readonly id: SignalLike = () => this.inputs.id(); - - /** The index of the tab. */ - readonly index = computed(() => this.inputs.tablist().inputs.items().indexOf(this)); - - /** The remote tabpanel unique identifier. */ - readonly value: SignalLike = () => this.inputs.value(); + readonly id: SignalLike; // set from inputs /** Whether the tab is disabled. */ - readonly disabled: SignalLike = () => this.inputs.disabled(); + readonly disabled: SignalLike; // set from inputs /** The html element that should receive focus. */ readonly element: SignalLike = () => this.inputs.element()!; @@ -55,28 +47,36 @@ export class TabPattern { /** Whether this tab has expandable panel. */ readonly expandable: SignalLike = () => true; - /** Whether the tab panel is expanded. */ - readonly expanded: WritableSignalLike; + /* + * Whether the tab panel is expanded. + * Primarily controlled by the behavior, which will read/write this value. + * The consumer of this pattern will instead only use the selectedTab input. + * The pattern will be responsible for synchronizing their state. + */ + readonly expanded: WritableSignalLike = linkedSignal( + () => this.inputs.tabList().selectedTab() === this, + ); /** Whether the tab is active. */ - readonly active = computed(() => this.inputs.tablist().inputs.activeItem() === this); + readonly active = computed(() => this.inputs.tabList().inputs.activeItem() === this); /** Whether the tab is selected. */ - readonly selected = computed(() => this.inputs.tablist().selectedTab() === this); + readonly selected = computed(() => this.inputs.tabList().selectedTab() === this); /** The tab index of the tab. */ - readonly tabIndex = computed(() => this.inputs.tablist().focusBehavior.getItemTabIndex(this)); + readonly tabIndex = computed(() => this.inputs.tabList().focusBehavior.getItemTabIndex(this)); /** The id of the tabpanel associated with the tab. */ - readonly controls = computed(() => this.inputs.tabpanel()?.id()); + readonly controls = computed(() => this.inputs.tabPanel()?.id()); constructor(readonly inputs: TabInputs) { - this.expanded = inputs.expanded; + this.id = inputs.id; + this.disabled = inputs.disabled; } /** Opens the tab. */ open(): boolean { - return this.inputs.tablist().open(this); + return this.inputs.tabList().open(this); } } @@ -86,19 +86,13 @@ export interface TabPanelInputs extends LabelControlOptionalInputs { id: SignalLike; /** The tab that controls this tabpanel. */ - tab: SignalLike; - - /** A local unique identifier for the tabpanel. */ - value: SignalLike; + readonly tab: SignalLike; } /** A tabpanel associated with a tab. */ export class TabPanelPattern { /** A global unique identifier for the tabpanel. */ - readonly id: SignalLike = () => this.inputs.id(); - - /** A local unique identifier for the tabpanel. */ - readonly value: SignalLike = () => this.inputs.value(); + readonly id: SignalLike; // set from inputs /** Controls label for this tabpanel. */ readonly labelManager: LabelControl; @@ -117,6 +111,8 @@ export class TabPanelPattern { ); constructor(readonly inputs: TabPanelInputs) { + this.id = inputs.id; + this.labelManager = new LabelControl({ ...inputs, defaultLabelledBy: computed(() => (this.inputs.tab() ? [this.inputs.tab()!.id()] : [])), @@ -131,6 +127,9 @@ export interface TabListInputs Omit { /** The selection strategy used by the tablist. */ selectionMode: SignalLike<'follow' | 'explicit'>; + + /** The currently selected tab. */ + selectedTab: WritableSignalLike; } /** Controls the state of a tablist. */ @@ -148,16 +147,16 @@ export class TabListPattern { readonly hasBeenInteracted = signal(false); /** The currently active tab. */ - readonly activeTab: SignalLike = () => this.inputs.activeItem(); + readonly activeTab: SignalLike; // set from inputs /** The currently selected tab. */ - readonly selectedTab: WritableSignalLike = signal(undefined); + readonly selectedTab: WritableSignalLike; // set from inputs /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'> = () => this.inputs.orientation(); + readonly orientation: SignalLike<'vertical' | 'horizontal'>; // set from inputs /** Whether the tablist is disabled. */ - readonly disabled: SignalLike = () => this.inputs.disabled(); + readonly disabled: SignalLike; // set from inputs /** The tab index of the tablist. */ readonly tabIndex = computed(() => this.focusBehavior.getListTabIndex()); @@ -211,6 +210,11 @@ export class TabListPattern { }); constructor(readonly inputs: TabListInputs) { + this.selectedTab = inputs.selectedTab; + this.activeTab = inputs.activeItem; + this.orientation = inputs.orientation; + this.disabled = inputs.disabled; + this.focusBehavior = new ListFocus(inputs); this.navigationBehavior = new ListNavigation({ @@ -280,19 +284,11 @@ export class TabListPattern { this.hasBeenInteracted.set(true); } - /** Opens the tab by given value. */ - open(value: string): boolean; - /** Opens the given tab or the current active tab. */ open(tab?: TabPattern): boolean; - - open(tab: TabPattern | string | undefined): boolean { + open(tab: TabPattern | undefined): boolean { tab ??= this.activeTab(); - if (typeof tab === 'string') { - tab = this.inputs.items().find(t => t.value() === tab); - } - if (tab === undefined) return false; const success = this.expansionBehavior.open(tab); diff --git a/src/aria/tabs/tab-list.ts b/src/aria/tabs/tab-list.ts index d9fda7adb7ce..df7c45c3654c 100644 --- a/src/aria/tabs/tab-list.ts +++ b/src/aria/tabs/tab-list.ts @@ -8,33 +8,34 @@ import {Directionality} from '@angular/cdk/bidi'; import { - booleanAttribute, - computed, Directive, ElementRef, + OnDestroy, + OnInit, + WritableSignal, + afterRenderEffect, + booleanAttribute, + computed, + effect, inject, input, model, signal, - afterRenderEffect, - OnInit, - OnDestroy, } from '@angular/core'; import {TabListPattern, TabPattern, sortDirectives} from '../private'; -import {TABS} from './tab-tokens'; -import type {Tab} from './tab'; +import {Tab} from './tab'; +import {TABS, TAB_LIST} from './tab-tokens'; /** * A TabList container. * - * The `ngTabList` directive controls a list of `ngTab` elements. It manages keyboard - * navigation, selection, and the overall orientation of the tabs. It should be placed - * within an `ngTabs` container. + * The `ngTabList` directive controls a list of `ngTab` elements, linked to their corresponding tab + * panels. It manages keyboard navigation, selection, and the overall orientation of the tabs. * * ```html - *
    - *
  • First Tab
  • - *
  • Second Tab
  • + *
      + *
    • First Tab
    • + *
    • Second Tab
    • *
    * ``` * @@ -55,6 +56,7 @@ import type {Tab} from './tab'; '(click)': '_pattern.onClick($event)', '(focusin)': '_pattern.onFocusIn()', }, + providers: [{provide: TAB_LIST, useExisting: TabList}], }) export class TabList implements OnInit, OnDestroy { /** A reference to the host element. */ @@ -63,23 +65,24 @@ export class TabList implements OnInit, OnDestroy { /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The parent Tabs. */ - private readonly _tabs = inject(TABS); + /** The parent Tabs container. */ + private readonly _tabsParent = inject(TABS); - /** The Tabs nested inside of the TabList. */ - private readonly _unorderedTabs = signal(new Set()); + /** The Tabs registered for this TabList. */ + private readonly _tabs = signal(new Set()); - /** Text direction. */ - readonly textDirection = inject(Directionality).valueSignal; + /** The Tabs registered for this TabList. */ + readonly _sortedTabs = computed(() => [...this._tabs()].sort(sortDirectives)); /** The Tab UIPatterns of the child Tabs. */ - readonly _tabPatterns = computed(() => - [...this._unorderedTabs()].sort(sortDirectives).map(tab => tab._pattern), - ); + private readonly _tabPatterns = computed(() => [...this._sortedTabs()].map(tab => tab._pattern)); /** Whether the tablist is vertically or horizontally oriented. */ readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); + /** Text direction. */ + readonly textDirection = inject(Directionality).valueSignal; + /** Whether focus should wrap when navigating. */ readonly wrap = input(true, {transform: booleanAttribute}); @@ -103,63 +106,67 @@ export class TabList implements OnInit, OnDestroy { */ readonly selectionMode = input<'follow' | 'explicit'>('follow'); - /** The current selected tab. */ + /** The current selected tab as a model input. */ readonly selectedTab = model(); + /** The current selected Tab pattern, passed to the List pattern. */ + private readonly _selectedTabPattern: WritableSignal = signal(undefined); + /** Whether the tablist is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); /** The TabList UIPattern. */ readonly _pattern: TabListPattern = new TabListPattern({ ...this, - items: this._tabPatterns, - activeItem: signal(undefined), element: () => this._elementRef.nativeElement, + activeItem: signal(undefined), + items: this._tabPatterns, + selectedTab: this._selectedTabPattern, }); constructor() { - afterRenderEffect(() => { - this._pattern.setDefaultStateEffect(); + effect(() => { + const tab = this.findTab(this.selectedTab()); + + this._selectedTabPattern.set(tab?._pattern); }); - afterRenderEffect(() => { - const tab = this._pattern.selectedTab(); - if (tab) { - this.selectedTab.set(tab.value()); - } + effect(() => { + const pattern = this._selectedTabPattern(); + const tab = this._sortedTabs().find(tab => tab._pattern == pattern); + + this.selectedTab.set(tab?.panel()?.id()); }); afterRenderEffect(() => { - const value = this.selectedTab(); - if (value) { - this._tabPatterns().forEach(tab => tab.expanded.set(false)); - const tab = this._tabPatterns().find(t => t.value() === value); - this._pattern.selectedTab.set(tab); - tab?.expanded.set(true); - } + this._pattern.setDefaultStateEffect(); }); } ngOnInit() { - this._tabs._register(this); + this._tabsParent._registerList(this); } ngOnDestroy() { - this._tabs._unregister(this); + this._tabsParent._registerList(this); + } + + _registerTab(child: Tab) { + this._tabs().add(child); + this._tabs.set(new Set(this._tabs())); } - _register(child: Tab) { - this._unorderedTabs().add(child); - this._unorderedTabs.set(new Set(this._unorderedTabs())); + _unregisterTab(child: Tab) { + this._tabs().delete(child); + this._tabs.set(new Set(this._tabs())); } - _unregister(child: Tab) { - this._unorderedTabs().delete(child); - this._unorderedTabs.set(new Set(this._unorderedTabs())); + /** Opens the tab panel with the specified id. */ + open(panelId: string): boolean { + return this._pattern.open(this.findTab(panelId)?._pattern); } - /** Opens the tab panel with the specified value. */ - open(value: string): boolean { - return this._pattern.open(value); + findTab(panelId?: string) { + return panelId ? this._sortedTabs().find(tab => tab.panel()?.id() === panelId) : undefined; } } diff --git a/src/aria/tabs/tab-panel.ts b/src/aria/tabs/tab-panel.ts index 13b5513c662f..44a34217f742 100644 --- a/src/aria/tabs/tab-panel.ts +++ b/src/aria/tabs/tab-panel.ts @@ -8,27 +8,29 @@ import {_IdGenerator} from '@angular/cdk/a11y'; import { - computed, Directive, ElementRef, + OnDestroy, + OnInit, + WritableSignal, + afterRenderEffect, + computed, inject, input, - afterRenderEffect, - OnInit, - OnDestroy, + signal, } from '@angular/core'; -import {TabPanelPattern, DeferredContentAware} from '../private'; +import {TabPattern, TabPanelPattern, DeferredContentAware} from '../private'; import {TABS} from './tab-tokens'; /** * A TabPanel container for the resources of layered content associated with a tab. * - * The `ngTabPanel` directive holds the content for a specific tab. It is linked to an - * `ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be + * The `ngTabPanel` directive holds the content for a specific tab. It will be referenced by an + * `ngTab`. If a tab panel is hidden, the `inert` attribute will be * applied to remove it from the accessibility tree. Proper styling is required for visual hiding. * * ```html - *
    + *
    * * * @@ -73,12 +75,7 @@ export class TabPanel implements OnInit, OnDestroy { readonly id = input(inject(_IdGenerator).getId('ng-tabpanel-', true)); /** The Tab UIPattern associated with the tabpanel */ - private readonly _tabPattern = computed(() => - this._tabs._tabPatterns()?.find(tab => tab.value() === this.value()), - ); - - /** A local unique identifier for the tabpanel. */ - readonly value = input.required(); + readonly _tabPattern: WritableSignal = signal(undefined); /** Whether the tab panel is visible. */ readonly visible = computed(() => !this._pattern.hidden()); @@ -94,10 +91,10 @@ export class TabPanel implements OnInit, OnDestroy { } ngOnInit() { - this._tabs._register(this); + this._tabs._registerPanel(this); } ngOnDestroy() { - this._tabs._unregister(this); + this._tabs._unregisterPanel(this); } } diff --git a/src/aria/tabs/tab-tokens.ts b/src/aria/tabs/tab-tokens.ts index 83b3ea089a6e..ee21244b165e 100644 --- a/src/aria/tabs/tab-tokens.ts +++ b/src/aria/tabs/tab-tokens.ts @@ -8,6 +8,10 @@ import {InjectionToken} from '@angular/core'; import type {Tabs} from './tabs'; +import type {TabList} from './tab-list'; /** Token used to expose the `Tabs` directive to child directives. */ export const TABS = new InjectionToken('TABS'); + +/** Token used to expose the tab list. */ +export const TAB_LIST = new InjectionToken('TAB_LIST'); diff --git a/src/aria/tabs/tab.ts b/src/aria/tabs/tab.ts index c446cec5a17a..d37dde74cb3f 100644 --- a/src/aria/tabs/tab.ts +++ b/src/aria/tabs/tab.ts @@ -8,28 +8,27 @@ import {_IdGenerator} from '@angular/cdk/a11y'; import { - booleanAttribute, - computed, Directive, ElementRef, + OnDestroy, + OnInit, + booleanAttribute, + computed, inject, input, - signal, - OnInit, - OnDestroy, } from '@angular/core'; import {TabPattern, HasElement} from '../private'; -import {TabList} from './tab-list'; -import {TABS} from './tab-tokens'; +import {TABS, TAB_LIST} from './tab-tokens'; +import {TabPanel} from './tab-panel'; /** * A selectable tab in a TabList. * * The `ngTab` directive represents an individual tab control within an `ngTabList`. It - * requires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`. + * requires a `panel` that references a corresponding `ngTabPanel`. * * ```html - *
  • + *
  • * My Tab Label *
  • * ``` @@ -58,29 +57,28 @@ export class Tab implements HasElement, OnInit, OnDestroy { /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The parent Tabs. */ - private readonly _tabs = inject(TABS); + /** The parent Tabs wrapper. */ + private readonly _tabsWrapper = inject(TABS); /** The parent TabList. */ - private readonly _tabList = inject(TabList); + private readonly _tabList = inject(TAB_LIST); /** A unique identifier for the widget. */ readonly id = input(inject(_IdGenerator).getId('ng-tab-', true)); - /** The parent TabList UIPattern. */ - private readonly _tablistPattern = computed(() => this._tabList._pattern); + /** Direct reference to or id of panel associated with this tab. */ + readonly panelRef = input.required({alias: 'panel'}); - /** The TabPanel UIPattern associated with the tab */ - private readonly _tabpanelPattern = computed(() => - this._tabs._unorderedTabpanelPatterns().find(tabpanel => tabpanel.value() === this.value()), - ); + /** The panel associated with this tab. */ + readonly panel = computed(() => { + const ref = this.panelRef(); + + return ref instanceof TabPanel ? ref : this._tabsWrapper.findTabPanel(ref); + }); /** Whether a tab is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); - /** The remote tabpanel unique identifier. */ - readonly value = input.required(); - /** Whether the tab is active. */ readonly active = computed(() => this._pattern.active()); @@ -90,10 +88,9 @@ export class Tab implements HasElement, OnInit, OnDestroy { /** The Tab UIPattern. */ readonly _pattern: TabPattern = new TabPattern({ ...this, - tablist: this._tablistPattern, - tabpanel: this._tabpanelPattern, - expanded: signal(false), element: () => this.element, + tabList: () => this._tabList._pattern, + tabPanel: computed(() => this.panel()?._pattern), }); /** Opens this tab panel. */ @@ -102,10 +99,10 @@ export class Tab implements HasElement, OnInit, OnDestroy { } ngOnInit() { - this._tabList._register(this); + this._tabList._registerTab(this); } ngOnDestroy() { - this._tabList._unregister(this); + this._tabList._unregisterTab(this); } } diff --git a/src/aria/tabs/tabs.spec.ts b/src/aria/tabs/tabs.spec.ts index 39c86b60afcf..661928c8a196 100644 --- a/src/aria/tabs/tabs.spec.ts +++ b/src/aria/tabs/tabs.spec.ts @@ -1,4 +1,4 @@ -import {Component, DebugElement, signal, ChangeDetectionStrategy} from '@angular/core'; +import {ChangeDetectionStrategy, Component, DebugElement, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Direction} from '@angular/cdk/bidi'; @@ -27,12 +27,10 @@ describe('Tabs', () => { let fixture: ComponentFixture; let testComponent: TestTabsComponent; - let tabsDebugElement: DebugElement; let tabListDebugElement: DebugElement; let tabDebugElements: DebugElement[]; let tabPanelDebugElements: DebugElement[]; - let tabsElement: HTMLElement; let tabListElement: HTMLElement; let tabElements: HTMLElement[]; let tabPanelElements: HTMLElement[]; @@ -107,12 +105,10 @@ describe('Tabs', () => { } function defineTestVariables() { - tabsDebugElement = fixture.debugElement.query(By.directive(Tabs)); tabListDebugElement = fixture.debugElement.query(By.directive(TabList)); tabDebugElements = fixture.debugElement.queryAll(By.directive(Tab)); tabPanelDebugElements = fixture.debugElement.queryAll(By.directive(TabPanel)); - tabsElement = tabsDebugElement.nativeElement; tabListElement = tabListDebugElement.nativeElement; tabElements = tabDebugElements.map(debugEl => debugEl.nativeElement); tabPanelElements = tabPanelDebugElements.map(debugEl => debugEl.nativeElement); @@ -127,8 +123,8 @@ describe('Tabs', () => { } afterEach(async () => { - if (tabsElement) { - await runAccessibilityChecks(tabsElement); + if (fixture.nativeElement) { + await runAccessibilityChecks(fixture.nativeElement); } }); @@ -726,14 +722,14 @@ describe('Tabs', () => { [focusMode]="focusMode()" [selectionMode]="selectionMode()"> @for (tabDef of tabsData(); track tabDef.value) { -
  • {{ tabDef.label }}
  • +
  • {{ tabDef.label }}
  • }
@for (tabDef of tabsData(); track tabDef.value) { -
+
{{ tabDef.content }} -
+
} `, diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index 33600a1c38b0..f0058c6b044e 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, Directive, ElementRef, inject, signal} from '@angular/core'; +import {Directive, ElementRef, computed, effect, inject, signal} from '@angular/core'; import {TabList} from './tab-list'; import {TabPanel} from './tab-panel'; import {TABS} from './tab-tokens'; -import {TabPanelPattern, TabPattern} from '../private'; /** * A Tabs container. @@ -55,39 +54,48 @@ export class Tabs { /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The TabList nested inside of the container. */ - private readonly _tablist = signal(undefined); + /** The TabList registered for this container. */ + private readonly _tabList = signal(undefined); - /** The TabPanels nested inside of the container. */ - private readonly _unorderedPanels = signal(new Set()); + /** The TabPanels registered for this container. */ + private readonly _tabPanels = signal(new Set()); - /** The Tab UIPattern of the child Tabs. */ - readonly _tabPatterns = computed(() => this._tablist()?._tabPatterns()); + /** The TabPanels registered for this container. */ + private readonly _tabPanelsList = computed(() => [...this._tabPanels()]); - /** The TabPanel UIPattern of the child TabPanels. */ - readonly _unorderedTabpanelPatterns = computed(() => - [...this._unorderedPanels()].map(tabpanel => tabpanel._pattern), - ); + constructor() { + effect(() => { + if (this._tabList()) { + for (const tab of this._tabList()!._sortedTabs()) { + const panel = this._tabPanelsList().find(panel => panel === tab.panel()); - _register(child: TabList | TabPanel) { - if (child instanceof TabList) { - this._tablist.set(child); - } + if (panel) { + panel._tabPattern.set(tab._pattern); + } + } + } + }); + } + + _registerList(list: TabList) { + this._tabList.set(list); + } - if (child instanceof TabPanel) { - this._unorderedPanels().add(child); - this._unorderedPanels.set(new Set(this._unorderedPanels())); - } + _unregisterList(list: TabList) { + this._tabList.set(undefined); } - _unregister(child: TabList | TabPanel) { - if (child instanceof TabList) { - this._tablist.set(undefined); - } + _registerPanel(panel: TabPanel) { + this._tabPanels().add(panel); + this._tabPanels.set(new Set(this._tabPanels())); + } + + _unregisterPanel(panel: TabPanel) { + this._tabPanels().delete(panel); + this._tabPanels.set(new Set(this._tabPanels())); + } - if (child instanceof TabPanel) { - this._unorderedPanels().delete(child); - this._unorderedPanels.set(new Set(this._unorderedPanels())); - } + findTabPanel(id?: string) { + return id ? this._tabPanelsList().find(panel => panel.id() === id) : undefined; } } diff --git a/src/aria/tabs/testing/tabs-harness.spec.ts b/src/aria/tabs/testing/tabs-harness.spec.ts index a4d0c8e77186..d815661ade74 100644 --- a/src/aria/tabs/testing/tabs-harness.spec.ts +++ b/src/aria/tabs/testing/tabs-harness.spec.ts @@ -132,21 +132,21 @@ describe('TabsHarness', () => { template: `
    -
  • Tab 1
  • -
  • Tab 2
  • -
  • Tab 3
  • +
  • Tab 1
  • +
  • Tab 2
  • +
  • Tab 3
-
+
Content 1
-
+
Content 2
-
+
Content 3
diff --git a/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.html b/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.html index 320cb123ad4c..38590b415617 100644 --- a/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.html +++ b/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.ts b/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.ts index 9edf19de666c..dce46112c822 100644 --- a/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.ts +++ b/src/components-examples/aria/tabs/active-descendant/tabs-active-descendant-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Active Descendant */ @Component({ selector: 'tabs-active-descendant-example', templateUrl: 'tabs-active-descendant-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsActiveDescendantExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html b/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html index cd2192fcf3cd..9c2ce53da99b 100644 --- a/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html +++ b/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.ts b/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.ts index 7cc4e6007c84..878b744b684e 100644 --- a/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.ts +++ b/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Disabled Tabs are Focusable */ @Component({ selector: 'tabs-disabled-focusable-example', templateUrl: 'tabs-disabled-focusable-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsDisabledFocusableExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html b/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html index e74ad12ae53b..8e541ffcd252 100644 --- a/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html +++ b/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.ts b/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.ts index 6ffcef883c33..ffabb7e884d1 100644 --- a/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.ts +++ b/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Disabled Tabs are Skipped */ @Component({ selector: 'tabs-disabled-skipped-example', templateUrl: 'tabs-disabled-skipped-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsDisabledSkippedExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/disabled/tabs-disabled-example.html b/src/components-examples/aria/tabs/disabled/tabs-disabled-example.html index d356a4f7f564..cccc8486e983 100644 --- a/src/components-examples/aria/tabs/disabled/tabs-disabled-example.html +++ b/src/components-examples/aria/tabs/disabled/tabs-disabled-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/disabled/tabs-disabled-example.ts b/src/components-examples/aria/tabs/disabled/tabs-disabled-example.ts index 5391f71df936..779a8eacc257 100644 --- a/src/components-examples/aria/tabs/disabled/tabs-disabled-example.ts +++ b/src/components-examples/aria/tabs/disabled/tabs-disabled-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Disabled */ @Component({ selector: 'tabs-disabled-example', templateUrl: 'tabs-disabled-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsDisabledExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.html b/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.html index 6ee281a4f16a..3f0db2fe4906 100644 --- a/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.html +++ b/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.ts b/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.ts index bd91d20cdc84..3de0fae84df1 100644 --- a/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.ts +++ b/src/components-examples/aria/tabs/explicit-selection/tabs-explicit-selection-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Explicit selection */ @Component({ selector: 'tabs-explicit-selection-example', templateUrl: 'tabs-explicit-selection-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsExplicitSelectionExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/rtl/tabs-rtl-example.html b/src/components-examples/aria/tabs/rtl/tabs-rtl-example.html index 2c0f2cad2ee4..2987242dee33 100644 --- a/src/components-examples/aria/tabs/rtl/tabs-rtl-example.html +++ b/src/components-examples/aria/tabs/rtl/tabs-rtl-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/rtl/tabs-rtl-example.ts b/src/components-examples/aria/tabs/rtl/tabs-rtl-example.ts index af28e8ec9c42..4857e4d65bf9 100644 --- a/src/components-examples/aria/tabs/rtl/tabs-rtl-example.ts +++ b/src/components-examples/aria/tabs/rtl/tabs-rtl-example.ts @@ -1,13 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Dir} from '@angular/cdk/bidi'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** * @title RTL */ @Component({ selector: 'tabs-rtl-example', templateUrl: 'tabs-rtl-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent, Dir], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsRtlExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html b/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html index 05407c3d0773..c14982d48fc5 100644 --- a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html +++ b/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html @@ -1,12 +1,12 @@
@for (i of tabsList; track i) { -
Tab {{i}}
+
Tab {{i}}
}
@for (i of tabsList; track i) { -
+
Content for Tab {{i}}
} diff --git a/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.html b/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.html index b92774de566b..7cedc1c89e43 100644 --- a/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.html +++ b/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.ts b/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.ts index 7e2d4cb079d6..31c045afc8d3 100644 --- a/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.ts +++ b/src/components-examples/aria/tabs/selection-follows-focus/tabs-selection-follows-focus-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Selection Follows Focus */ @Component({ selector: 'tabs-selection-follows-focus-example', templateUrl: 'tabs-selection-follows-focus-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsSelectionFollowsFocusExample { tabs = viewChildren(Tab); diff --git a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html index 74effd360596..350fe61df411 100644 --- a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html +++ b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html @@ -29,12 +29,10 @@ Tab selection - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - Tab 5 + + @for (tabDef of tabsData; track tabDef.value) { + {{ tabDef.label }} + }
@@ -47,33 +45,17 @@ [softDisabled]="softDisabled.value" [orientation]="orientation" [focusMode]="focusMode" + [selectedTab]="selectedTab" selectionMode="explicit" - [selectedTab]="tabSelection" > -
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+ @for (tabDef of tabsData; track tabDef.value) { +
{{ tabDef.label }}
+ }
-
- Panel 1 -
- -
- Panel 2 -
- -
- Panel 3 -
- -
- Panel 4 -
- -
- Panel 5 -
+ @for (tabDef of tabsData; track tabDef.value) { +
+ {{ tabDef.label }} +
+ }
diff --git a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts index bf80136ee106..68608bfa38aa 100644 --- a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts +++ b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts @@ -1,6 +1,6 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; import {MatCheckboxModule} from '@angular/material/checkbox'; -import {Tabs, TabList, Tab, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatSelectModule} from '@angular/material/select'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; @@ -26,7 +26,7 @@ export class TabsConfigurableExample { orientation: 'vertical' | 'horizontal' = 'horizontal'; focusMode: 'roving' | 'activedescendant' = 'roving'; selectionMode: 'explicit' | 'follow' = 'follow'; - tabSelection = 'tab-1'; + selectedTab = 'tab1'; wrap = new FormControl(true, {nonNullable: true}); disabled = new FormControl(false, {nonNullable: true}); @@ -34,6 +34,14 @@ export class TabsConfigurableExample { tabs = viewChildren(Tab); + tabsData = [ + {label: 'Tab 1', value: 'tab1'}, + {label: 'Tab 2', value: 'tab2'}, + {label: 'Tab 3', value: 'tab3'}, + {label: 'Tab 4', value: 'tab4'}, + {label: 'Tab 5', value: 'tab5'}, + ]; + constructor() { afterRenderEffect(() => { const tab = this.tabs().find(tab => tab.active()); diff --git a/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.html b/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.html index 132f776c106c..bddc843b90d4 100644 --- a/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.html +++ b/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.html @@ -1,29 +1,29 @@
-
-
Tab 1
-
Tab 2
-
Tab 3
-
Tab 4
-
Tab 5
+
+
Tab 1
+
Tab 2
+
Tab 3
+
Tab 4
+
Tab 5
-
+
Panel 1
-
+
Panel 2
-
+
Panel 3
-
+
Panel 4
-
+
Panel 5
diff --git a/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.ts b/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.ts index 626a1b9bee3d..31b619c11170 100644 --- a/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.ts +++ b/src/components-examples/aria/tabs/vertical-orientation/tabs-vertical-example.ts @@ -1,12 +1,12 @@ import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Tabs, Tab, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; /** @title Vertical */ @Component({ selector: 'tabs-vertical-example', templateUrl: 'tabs-vertical-example.html', styleUrls: ['../tabs-common.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], + imports: [Tabs, TabList, Tab, TabPanel, TabContent], }) export class TabsVerticalExample { tabs = viewChildren(Tab);