Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions goldens/aria/accordion/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 { Signal } from '@angular/core';

// @public
export class AccordionContent {
Expand All @@ -18,17 +19,19 @@ export class AccordionContent {
}

// @public
export class AccordionGroup {
export class AccordionGroup implements OnDestroy {
constructor();
collapseAll(): void;
readonly _collection: SortedCollection<AccordionTrigger>;
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly element: HTMLElement;
expandAll(): void;
readonly multiExpandable: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
ngOnDestroy(): void;
readonly _pattern: AccordionGroupPattern;
_registerTrigger(trigger: AccordionTrigger): void;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
_unregisterTrigger(trigger: AccordionTrigger): void;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionGroup, "[ngAccordionGroup]", ["ngAccordionGroup"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "multiExpandable": { "alias": "multiExpandable"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
Expand Down Expand Up @@ -61,7 +64,6 @@ export class AccordionTrigger implements OnInit, OnDestroy {
expand(): void;
readonly expanded: _angular_core.ModelSignal<boolean>;
readonly id: _angular_core.InputSignal<string>;
readonly index: _angular_core.InputSignal<number | undefined>;
// (undocumented)
ngOnDestroy(): void;
// (undocumented)
Expand All @@ -71,7 +73,7 @@ export class AccordionTrigger implements OnInit, OnDestroy {
_pattern: AccordionTriggerPattern;
toggle(): void;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionTrigger, "[ngAccordionTrigger]", ["ngAccordionTrigger"], { "panel": { "alias": "panel"; "required": true; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; }, never, never, true, never>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionTrigger, "[ngAccordionTrigger]", ["ngAccordionTrigger"], { "panel": { "alias": "panel"; "required": true; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; }, never, never, true, never>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AccordionTrigger, never>;
}
Expand Down
17 changes: 13 additions & 4 deletions goldens/aria/listbox/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@

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 { Signal } from '@angular/core';

// @public
export class Listbox<V> {
export class Listbox<V> implements OnDestroy {
constructor();
readonly _collection: SortedCollection<Option_2<V>>;
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly element: HTMLElement;
readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">;
gotoFirst(): void;
readonly id: _angular_core.InputSignal<string>;
protected readonly items: _angular_core.Signal<OptionPattern<V>[]>;
readonly multi: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
ngOnDestroy(): void;
readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">;
readonly _pattern: ListboxPattern<V>;
readonly readonly: _angular_core.InputSignalWithTransform<boolean, unknown>;
Expand All @@ -29,18 +34,22 @@ export class Listbox<V> {
readonly value: _angular_core.ModelSignal<V[]>;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Listbox<any>, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, ["_options"], never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Listbox<any>, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Listbox<any>, never>;
}

// @public
class Option_2<V> {
class Option_2<V> implements OnInit, OnDestroy {
readonly active: _angular_core.Signal<boolean>;
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly element: HTMLElement;
readonly id: _angular_core.InputSignal<string>;
readonly label: _angular_core.InputSignal<string | undefined>;
// (undocumented)
ngOnDestroy(): void;
// (undocumented)
ngOnInit(): void;
readonly _pattern: OptionPattern<V>;
readonly selected: _angular_core.Signal<boolean | undefined>;
readonly value: _angular_core.InputSignal<V>;
Expand Down
29 changes: 14 additions & 15 deletions goldens/aria/tabs/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 { Signal } from '@angular/core';
import { WritableSignal } from '@angular/core';

// @public
Expand All @@ -21,7 +22,6 @@ export class Tab implements HasElement, OnInit, OnDestroy {
// (undocumented)
ngOnInit(): void;
open(): void;
readonly panel: _angular_core.Signal<TabPanel | undefined>;
readonly _pattern: TabPattern;
readonly selected: _angular_core.Signal<boolean>;
readonly value: _angular_core.InputSignal<string>;
Expand All @@ -42,6 +42,7 @@ export class TabContent {
// @public
export class TabList implements OnInit, OnDestroy {
constructor();
readonly _collection: SortedCollection<Tab>;
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly element: HTMLElement;
// (undocumented)
Expand All @@ -54,15 +55,12 @@ export class TabList implements OnInit, OnDestroy {
open(value: string): boolean;
readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">;
readonly _pattern: TabListPattern;
// (undocumented)
_registerTab(child: Tab): void;
readonly selectedTab: _angular_core.ModelSignal<string | undefined>;
readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly _sortedTabs: _angular_core.Signal<Tab[]>;
readonly _tabPatterns: _angular_core.Signal<TabPattern[]>;
readonly _tabsParent: Tabs;
readonly textDirection: WritableSignal<_angular_cdk_bidi.Direction>;
// (undocumented)
_unregisterTab(child: Tab): void;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TabList, "[ngTabList]", ["ngTabList"], { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "selectedTab": { "alias": "selectedTab"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "selectedTab": "selectedTabChange"; }, never, never, true, never>;
Expand All @@ -80,7 +78,6 @@ export class TabPanel implements OnInit, OnDestroy {
// (undocumented)
ngOnInit(): void;
readonly _pattern: TabPanelPattern;
readonly _tabPattern: WritableSignal<TabPattern | undefined>;
readonly value: _angular_core.InputSignal<string>;
readonly visible: _angular_core.Signal<boolean>;
// (undocumented)
Expand All @@ -90,19 +87,21 @@ export class TabPanel implements OnInit, OnDestroy {
}

// @public
export class Tabs {
export class Tabs implements OnDestroy {
constructor();
readonly _collection: SortedCollection<TabPanel>;
readonly element: HTMLElement;
// (undocumented)
findTabPanel(value?: string): TabPanel | undefined;
// (undocumented)
_registerList(list: TabList): void;
// (undocumented)
_registerPanel(panel: TabPanel): void;
ngOnDestroy(): void;
readonly _panelMap: _angular_core.Signal<Map<string, TabPanelPattern>>;
// (undocumented)
_unregisterList(list: TabList): void;
_register(child: TabList): void;
readonly _tabList: _angular_core.WritableSignal<TabList | undefined>;
readonly _tabMap: _angular_core.Signal<Map<string, TabPattern>>;
readonly _tabPanelPatterns: _angular_core.Signal<TabPanelPattern[]>;
readonly _tabPatterns: _angular_core.Signal<TabPattern[] | undefined>;
// (undocumented)
_unregisterPanel(panel: TabPanel): void;
_unregister(): void;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Tabs, "[ngTabs]", ["ngTabs"], {}, {}, never, never, true, never>;
// (undocumented)
Expand Down
2 changes: 2 additions & 0 deletions src/aria/accordion/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ng_project(
deps = [
"//:node_modules/@angular/core",
"//src/aria/private",
"//src/aria/private/utils",
"//src/cdk/a11y",
"//src/cdk/bidi",
"//src/cdk/testing",
Expand All @@ -27,6 +28,7 @@ ng_project(
":accordion",
"//:node_modules/@angular/core",
"//:node_modules/@angular/platform-browser",
"//src/aria/private/testing",
"//src/cdk/testing",
"//src/cdk/testing/private",
"//src/cdk/testing/testbed",
Expand Down
53 changes: 23 additions & 30 deletions src/aria/accordion/accordion-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import {
inject,
input,
signal,
afterNextRender,
OnDestroy,
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {AccordionGroupPattern, sortDirectives} from '../private';
import {AccordionGroupPattern} from '../private';
import {SortedCollection} from '../private/utils/collection';
import {ACCORDION_GROUP, ACCORDION_COLLECTION} from './accordion-tokens';
import {AccordionTrigger} from './accordion-trigger';
import {ACCORDION_GROUP} from './accordion-tokens';

/**
* A container for a group of accordion items. It manages the overall state and
Expand Down Expand Up @@ -64,32 +67,24 @@ import {ACCORDION_GROUP} from './accordion-tokens';
'(click)': '_pattern.onClick($event)',
'(focusin)': '_pattern.onFocus($event)',
},
providers: [{provide: ACCORDION_GROUP, useExisting: AccordionGroup}],
providers: [
{provide: ACCORDION_GROUP, useExisting: AccordionGroup},
{provide: ACCORDION_COLLECTION, useFactory: () => inject(AccordionGroup)._collection},
],
})
export class AccordionGroup {
export class AccordionGroup implements OnDestroy {
/** A reference to the group element. */
private readonly _elementRef = inject(ElementRef);

/** A reference to the group element. */
readonly element = this._elementRef.nativeElement as HTMLElement;

/** The AccordionTriggers nested inside this group. */
private readonly _triggers = signal(new Set<AccordionTrigger>());

/** The AccordionTriggers nested inside this group. */
private readonly _sortedTriggers = computed(() => {
const triggers = [...this._triggers()] as AccordionTrigger[];
const sortFn =
triggers[0]?.index() === undefined
? sortDirectives
: (a: AccordionTrigger, b: AccordionTrigger) => a.index()! - b.index()!;

return triggers.sort(sortFn);
});
/** The collection of AccordionTriggers. */
readonly _collection = new SortedCollection<AccordionTrigger>();

/** The corresponding patterns for the accordion triggers. */
private readonly _triggerPatterns = computed(() => {
return this._sortedTriggers().map(t => t._pattern);
return this._collection.orderedItems().map(t => t._pattern);
});

/** The text direction (ltr or rtl). */
Expand Down Expand Up @@ -119,6 +114,16 @@ export class AccordionGroup {
orientation: () => 'vertical',
});

constructor() {
afterNextRender(() => {
this._collection.startObserving(this.element);
});
}

ngOnDestroy() {
this._collection.stopObserving();
}

/** Expands all accordion panels if multi-expandable. */
expandAll() {
this._pattern.expandAll();
Expand All @@ -128,16 +133,4 @@ export class AccordionGroup {
collapseAll() {
this._pattern.collapseAll();
}

/** Internal method to register each trigger as we can not use contentChildren. */
_registerTrigger(trigger: AccordionTrigger) {
this._triggers().add(trigger);
this._triggers.set(new Set(this._triggers()));
}

/** Internal method to unregister each trigger as we can not use contentChildren. */
_unregisterTrigger(trigger: AccordionTrigger) {
this._triggers().delete(trigger);
this._triggers.set(new Set(this._triggers()));
}
}
6 changes: 6 additions & 0 deletions src/aria/accordion/accordion-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

import {InjectionToken} from '@angular/core';
import type {AccordionGroup} from './accordion-group';
import {SortedCollection} from '../private/utils/collection';
import type {AccordionTrigger} from './accordion-trigger';

/** Token used to expose the accordion group. */
export const ACCORDION_GROUP = new InjectionToken<AccordionGroup>('ACCORDION_GROUP');

export const ACCORDION_COLLECTION = new InjectionToken<SortedCollection<AccordionTrigger>>(
'ACCORDION_COLLECTION',
);
12 changes: 6 additions & 6 deletions src/aria/accordion/accordion-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from '@angular/core';
import {_IdGenerator} from '@angular/cdk/a11y';
import {AccordionTriggerPattern} from '../private';
import {ACCORDION_GROUP} from './accordion-tokens';
import {ACCORDION_GROUP, ACCORDION_COLLECTION} from './accordion-tokens';
import {AccordionPanel} from './accordion-panel';

/**
Expand Down Expand Up @@ -64,6 +64,9 @@ export class AccordionTrigger implements OnInit, OnDestroy {
/** The parent AccordionGroup. */
private readonly _accordionGroup = inject(ACCORDION_GROUP);

/** The parent collection. */
private readonly _collection = inject(ACCORDION_COLLECTION);

/** The associated AccordionPanel. */
readonly panel = input.required<AccordionPanel>();

Expand All @@ -76,9 +79,6 @@ export class AccordionTrigger implements OnInit, OnDestroy {
/** Whether the trigger is disabled. */
readonly disabled = input(false, {transform: booleanAttribute});

/** The index of the trigger within the accordion group. */
readonly index = input<number>();

/** Whether the corresponding panel is expanded. */
readonly expanded = model<boolean>(false);

Expand All @@ -98,13 +98,13 @@ export class AccordionTrigger implements OnInit, OnDestroy {

this.panel()._pattern = this._pattern;

this._accordionGroup._registerTrigger(this);
this._collection.register(this);
}

ngOnDestroy() {
this.panel()._pattern = undefined;

this._accordionGroup._unregisterTrigger(this);
this._collection.unregister(this);
}

/** Expands this item. */
Expand Down
Loading
Loading