From b9f120728683c0e062b76f64091fa034bfcebd16 Mon Sep 17 00:00:00 2001 From: Andrey Dolgachev Date: Mon, 27 Oct 2025 10:00:09 -0700 Subject: [PATCH 1/5] refactor(aria/accordion): Extend public api with expansion methods --- src/aria/accordion/accordion.ts | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/aria/accordion/accordion.ts b/src/aria/accordion/accordion.ts index b98e26cd0a82..2b1b19bfbef4 100644 --- a/src/aria/accordion/accordion.ts +++ b/src/aria/accordion/accordion.ts @@ -76,6 +76,21 @@ export class AccordionPanel { this._deferredContentAware.contentVisible.set(!this._pattern.hidden()); }); } + + /** Expands this item. */ + expand() { + this.accordionTrigger()?.expansionControl.open(); + } + + /** Collapses this item. */ + collapse() { + this.accordionTrigger()?.expansionControl.close(); + } + + /** Toggles the expansion state of this item. */ + toggle() { + this.accordionTrigger()?.expansionControl.toggle(); + } } /** @@ -135,6 +150,21 @@ export class AccordionTrigger { accordionGroup: computed(() => this._accordionGroup._pattern), accordionPanel: this.accordionPanel, }); + + /** Expands this item. */ + expand() { + this._pattern.expansionControl.open(); + } + + /** Collapses this item. */ + collapse() { + this._pattern.expansionControl.close(); + } + + /** Toggles the expansion state of this item. */ + toggle() { + this._pattern.expansionControl.toggle(); + } } /** @@ -204,6 +234,16 @@ export class AccordionGroup { } }); } + + /** Expands all accordion panels if multi-expandable. */ + expandAll() { + this._pattern.expansionManager.openAll(); + } + + /** Collapses all accordion panels. */ + collapseAll() { + this._pattern.expansionManager.closeAll(); + } } /** From 195d4a1fd53016fca675f5041a13d860a8225d32 Mon Sep 17 00:00:00 2001 From: Andrey Dolgachev Date: Tue, 28 Oct 2025 16:59:10 -0700 Subject: [PATCH 2/5] refactor(aria/menu): Extend public api with open/close methods --- src/aria/menu/menu.ts | 39 ++++++++++++++++++++++++----------- src/aria/private/menu/menu.ts | 19 +++++++++++------ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index 5c5ba845fa95..eac786d8158c 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -83,6 +83,16 @@ export class MenuTrigger { onFocusIn() { this.hasBeenFocused.set(true); } + + /** Opens the menu. */ + open(opts?: {first?: boolean; last?: boolean}) { + this._pattern.open(opts); + } + + /** Closes the menu. */ + close(opts: {refocus?: boolean} = {}) { + this._pattern.close(opts); + } } /** @@ -222,24 +232,14 @@ export class Menu { }); } - // TODO(wagnermaciel): Author close, closeAll, and open methods for each directive. - /** Closes the menu. */ close(opts?: {refocus?: boolean}) { - this._pattern.inputs.parent()?.close(opts); + this._pattern.close(opts); } /** Closes all parent menus. */ closeAll(opts?: {refocus?: boolean}) { - const root = this._pattern.root(); - - if (root instanceof MenuTriggerPattern) { - root.close(opts); - } - - if (root instanceof MenuPattern || root instanceof MenuBarPattern) { - root.inputs.activeItem()?.close(opts); - } + this._pattern.closeAll(opts); } } @@ -325,6 +325,11 @@ export class MenuBar { } }); } + + /** Closes the menubar and refocuses the root menu bar item. */ + close(opts?: {refocus?: boolean}) { + this._pattern.close(opts); + } } /** @@ -402,6 +407,16 @@ export class MenuItem { onFocusIn() { this.hasBeenFocused.set(true); } + + /** Opens the submenu. */ + open(opts?: {first?: boolean; last?: boolean}) { + this._pattern.open(opts); + } + + /** Closes the submenu. */ + close(opts: {refocus?: boolean} = {}) { + this._pattern.close(opts); + } } /** Defers the rendering of the menu content. */ diff --git a/src/aria/private/menu/menu.ts b/src/aria/private/menu/menu.ts index e49dfa1e5dbc..82b05e90d263 100644 --- a/src/aria/private/menu/menu.ts +++ b/src/aria/private/menu/menu.ts @@ -136,7 +136,7 @@ export class MenuPattern { .on('Home', () => this.first()) .on('End', () => this.last()) .on('Enter', () => this.trigger()) - .on('Escape', () => this.closeAll()) + .on('Escape', () => this.closeAll({refocus: true})) .on(this._expandKey, () => this.expand()) .on(this._collapseKey, () => this.collapse()) .on(this.dynamicSpaceKey, () => this.trigger()) @@ -345,12 +345,17 @@ export class MenuPattern { } } + /** Closes the menu. */ + close(opts?: {refocus?: boolean}) { + this.inputs.parent()?.close(opts); + } + /** Closes the menu and all parent menus. */ - closeAll() { + closeAll(opts?: {refocus?: boolean}) { const root = this.root(); if (root instanceof MenuTriggerPattern) { - root.close({refocus: true}); + root.close(opts); } if (root instanceof MenuBarPattern) { @@ -358,7 +363,7 @@ export class MenuPattern { } if (root instanceof MenuPattern) { - root.inputs.activeItem()?.close({refocus: true}); + root.inputs.activeItem()?.close(opts); } } } @@ -496,8 +501,10 @@ export class MenuBarPattern { } /** Closes the menubar and refocuses the root menu bar item. */ - close() { - this.inputs.activeItem()?.close({refocus: this.isFocused()}); + close(opts?: {refocus?: boolean}) { + opts ??= {refocus: this.isFocused()}; + + this.inputs.activeItem()?.close(opts); } } From cad9ce6f3f581c9ed36e1292114c3be17f225554 Mon Sep 17 00:00:00 2001 From: Andrey Dolgachev Date: Tue, 28 Oct 2025 21:03:48 -0700 Subject: [PATCH 3/5] refactor(aria/tabs): Extend public api with open methods --- src/aria/tabs/tabs.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index f6442a604f70..356ec2ab6d47 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -107,6 +107,17 @@ export class Tabs { this._unorderedPanels.set(new Set(this._unorderedPanels())); } } + + /** Opens the tab panel with the specified value. */ + open(value: string) { + const tab = this._findTabPatternByValue(value); + + tab?.expansion.open(); + } + + _findTabPatternByValue(value: string) { + return this.tabs()?.find(t => t.value() === value); + } } /** @@ -272,6 +283,11 @@ export class Tab implements HasElement, OnInit, OnDestroy { value: this.value, }); + /** Opens this tab panel. */ + open() { + this._pattern.expansion.open(); + } + ngOnInit() { this._tabList.register(this); } From e8300dae697bbc5e60d45e203268d3d48aa94474 Mon Sep 17 00:00:00 2001 From: Andrey Dolgachev Date: Wed, 29 Oct 2025 19:23:54 -0700 Subject: [PATCH 4/5] refactor(aria/tree): Extend public api with expansion methods --- src/aria/tree/tree.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/aria/tree/tree.ts b/src/aria/tree/tree.ts index 9ff8707e8447..2aa7d7a3fcdb 100644 --- a/src/aria/tree/tree.ts +++ b/src/aria/tree/tree.ts @@ -205,6 +205,16 @@ export class Tree { scrollActiveItemIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) { this._pattern.inputs.activeItem()?.element().scrollIntoView(options); } + + /** Expands all tree items if multi-expandable. */ + expandAll() { + this._pattern.expansionManager.openAll(); + } + + /** Collapses all tree items. */ + collapseAll() { + this._pattern.expansionManager.closeAll(); + } } /** From bff91ffa80568cfca29bcf6d9ff1d4e2a73c2f86 Mon Sep 17 00:00:00 2001 From: Andrey Dolgachev Date: Sun, 2 Nov 2025 20:08:44 -0800 Subject: [PATCH 5/5] refactor(aria/combobox): Extend public api with open/close/select methods --- src/aria/combobox/combobox.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/aria/combobox/combobox.ts b/src/aria/combobox/combobox.ts index 88dd6d5b1f08..ded912105792 100644 --- a/src/aria/combobox/combobox.ts +++ b/src/aria/combobox/combobox.ts @@ -113,6 +113,21 @@ export class Combobox { } }); } + + /** Opens the combobox. */ + open(nav?: {first?: boolean; last?: boolean; selected?: boolean}) { + this._pattern.open(nav); + } + + /** Closes the combobox. */ + close(opts?: {reset: boolean}) { + this._pattern.close(opts); + } + + /** Selects an item in the combobox popup. */ + select(opts: {item?: V; commit?: boolean; close?: boolean} = {}) { + this._pattern.select(opts); + } } @Directive({