diff --git a/src/aria/accordion/accordion.ts b/src/aria/accordion/accordion.ts index 0d456bac4d3a..76f3d792baf1 100644 --- a/src/aria/accordion/accordion.ts +++ b/src/aria/accordion/accordion.ts @@ -47,7 +47,7 @@ import { 'role': 'region', '[attr.id]': '_pattern.id()', '[attr.aria-labelledby]': '_pattern.accordionTrigger()?.id()', - '[attr.inert]': '_pattern.hidden() ? true : null', + '[attr.inert]': '!visible() ? true : null', }, }) export class AccordionPanel { @@ -60,6 +60,9 @@ export class AccordionPanel { /** A local unique identifier for the panel, used to match with its trigger's value. */ value = input.required(); + /** Whether the accordion panel is visible. True if the associated trigger is expanded. */ + readonly visible = computed(() => !this._pattern.hidden()); + /** The parent accordion trigger pattern that controls this panel. This is set by AccordionGroup. */ readonly accordionTrigger: WritableSignal = signal(undefined); @@ -74,7 +77,7 @@ export class AccordionPanel { constructor() { // Connect the panel's hidden state to the DeferredContentAware's visibility. afterRenderEffect(() => { - this._deferredContentAware.contentVisible.set(!this._pattern.hidden()); + this._deferredContentAware.contentVisible.set(this.visible()); }); } } @@ -88,10 +91,10 @@ export class AccordionPanel { exportAs: 'ngAccordionTrigger', host: { 'class': 'ng-accordion-trigger', - '[attr.data-active]': '_pattern.active()', + '[attr.data-active]': 'active()', 'role': 'button', '[id]': '_pattern.id()', - '[attr.aria-expanded]': '_pattern.expanded()', + '[attr.aria-expanded]': 'expanded()', '[attr.aria-controls]': '_pattern.controls()', '[attr.aria-disabled]': '_pattern.disabled()', '[attr.disabled]': 'hardDisabled() ? true : null', @@ -117,6 +120,12 @@ export class AccordionTrigger { /** Whether the trigger is disabled. */ disabled = input(false, {transform: booleanAttribute}); + /** Whether the trigger is active. */ + readonly active = computed(() => this._pattern.active()); + + /** Whether the trigger is expanded. */ + readonly expanded = computed(() => this._pattern.expanded()); + /** * Whether this trigger is completely inaccessible. * diff --git a/src/aria/combobox/combobox.ts b/src/aria/combobox/combobox.ts index e583db721c60..33883634e3de 100644 --- a/src/aria/combobox/combobox.ts +++ b/src/aria/combobox/combobox.ts @@ -86,6 +86,9 @@ export class Combobox { /** Whether the combobox is expanded. */ readonly expanded = computed(() => this._pattern.expanded()); + /** The currently highlighted item in the combobox. */ + readonly highlightedItem = computed(() => this._pattern.highlightedItem()); + /** Input element connected to the combobox, if any. */ readonly inputElement = computed(() => this._pattern.inputs.inputEl()); diff --git a/src/aria/grid/grid.ts b/src/aria/grid/grid.ts index 91f5148ff71a..3ca68b621d70 100644 --- a/src/aria/grid/grid.ts +++ b/src/aria/grid/grid.ts @@ -77,6 +77,21 @@ export class Grid { /** The wrapping behavior for keyboard navigation along the column axis. */ readonly colWrap = input<'continuous' | 'loop' | 'nowrap'>('loop'); + /** Whether multiple cells in the grid can be selected. */ + readonly multi = input(false, {transform: booleanAttribute}); + + /** The selection strategy used by the grid. */ + readonly selectionMode = input<'follow' | 'explicit'>('follow'); + + /** Whether enable range selections (with modifier keys or dragging). */ + readonly enableRangeSelection = input(false, {transform: booleanAttribute}); + + /** Whether the user is currently dragging to select a range of cells. */ + readonly dragging = computed(() => this._pattern.dragging()); + + /** Whether the focus is in the grid. */ + readonly isFocused = computed(() => this._pattern.isFocused()); + /** The UI pattern for the grid. */ readonly _pattern = new GridPattern({ ...this, diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index 062a6487d208..92cbf63413d1 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -45,9 +45,9 @@ import {Directionality} from '@angular/cdk/bidi'; exportAs: 'ngMenuTrigger', host: { 'class': 'ng-menu-trigger', - '[attr.tabindex]': '_pattern.tabindex()', - '[attr.aria-haspopup]': '_pattern.hasPopup()', - '[attr.aria-expanded]': '_pattern.expanded()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-haspopup]': 'hasPopup()', + '[attr.aria-expanded]': 'expanded()', '[attr.aria-controls]': '_pattern.menu()?.id()', '(click)': '_pattern.onClick()', '(keydown)': '_pattern.onKeydown($event)', @@ -70,6 +70,12 @@ export class MenuTrigger { /** Whether the menu item has been focused. */ readonly hasBeenFocused = signal(false); + /** Whether the menu is expanded. */ + readonly expanded = computed(() => this._pattern.expanded()); + + /** Whether the menu trigger has a popup. */ + readonly hasPopup = computed(() => this._pattern.hasPopup()); + /** The menu trigger ui pattern instance. */ _pattern: MenuTriggerPattern = new MenuTriggerPattern({ element: computed(() => this._elementRef.nativeElement), @@ -109,7 +115,7 @@ export class MenuTrigger { 'role': 'menu', 'class': 'ng-menu', '[attr.id]': '_pattern.id()', - '[attr.data-visible]': '_pattern.isVisible()', + '[attr.data-visible]': 'isVisible()', '(keydown)': '_pattern.onKeydown($event)', '(mouseover)': '_pattern.onMouseOver($event)', '(mouseout)': '_pattern.onMouseOut($event)', @@ -174,8 +180,11 @@ export class Menu { */ readonly items = () => this._items().map(i => i._pattern); + /** Whether the menu or any of its child elements are currently focused. */ + readonly isFocused = computed(() => this._pattern.isFocused()); + /** Whether the menu is visible. */ - isVisible = computed(() => this._pattern.isVisible()); + readonly isVisible = computed(() => this._pattern.isVisible()); /** A callback function triggered when a menu item is selected. */ onSelect = output(); @@ -294,6 +303,9 @@ export class MenuBar { /** The delay in seconds before the typeahead buffer is cleared. */ readonly typeaheadDelay = input(0.5); + /** Whether the menubar or any of its child elements are currently focused. */ + readonly isFocused = computed(() => this._pattern.isFocused()); + /** The menu ui pattern instance. */ readonly _pattern: MenuBarPattern; @@ -340,10 +352,10 @@ export class MenuBar { 'role': 'menuitem', 'class': 'ng-menu-item', '(focusin)': 'onFocusIn()', - '[attr.tabindex]': '_pattern.tabindex()', - '[attr.data-active]': '_pattern.isActive()', - '[attr.aria-haspopup]': '_pattern.hasPopup()', - '[attr.aria-expanded]': '_pattern.expanded()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.data-active]': 'isActive()', + '[attr.aria-haspopup]': 'hasPopup()', + '[attr.aria-expanded]': 'expanded()', '[attr.aria-disabled]': '_pattern.disabled()', '[attr.aria-controls]': '_pattern.submenu()?.id()', }, @@ -381,9 +393,18 @@ export class MenuItem { /** The submenu associated with the menu item. */ readonly submenu = input | undefined>(undefined); + /** Whether the menu item is active. */ + readonly isActive = computed(() => this._pattern.isActive()); + /** Whether the menu item has been focused. */ readonly hasBeenFocused = signal(false); + /** Whether the menuis expanded. */ + readonly expanded = computed(() => this._pattern.expanded()); + + /** Whether the menu item has a popup. */ + readonly hasPopup = computed(() => this._pattern.hasPopup()); + /** The menu item ui pattern instance. */ readonly _pattern: MenuItemPattern = new MenuItemPattern({ id: this.id, diff --git a/src/aria/private/tree/tree.ts b/src/aria/private/tree/tree.ts index 2b21c2ac9b27..3f5542cd7c17 100644 --- a/src/aria/private/tree/tree.ts +++ b/src/aria/private/tree/tree.ts @@ -185,7 +185,7 @@ export class TreePattern { /** The root is always expanded. */ readonly expanded = () => true; - /** The roow is always visible. */ + /** The root is always visible. */ readonly visible = () => true; /** The tabindex of the tree. */ diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index 0e4442f6d8aa..23bbd7f03df3 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -230,10 +230,10 @@ export class TabList implements OnInit, OnDestroy { host: { 'role': 'tab', 'class': 'ng-tab', - '[attr.data-active]': '_pattern.active()', + '[attr.data-active]': 'active()', '[attr.id]': '_pattern.id()', - '[attr.tabindex]': '_pattern.tabindex()', - '[attr.aria-selected]': '_pattern.selected()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-selected]': 'selected()', '[attr.aria-disabled]': '_pattern.disabled()', '[attr.aria-controls]': '_pattern.controls()', }, @@ -268,6 +268,15 @@ export class Tab implements HasElement, OnInit, OnDestroy { /** A local unique identifier for the tab. */ readonly value = input.required(); + /** Whether the tab is active. */ + readonly active = computed(() => this._pattern.active()); + + /** Whether the tab is expanded. */ + readonly expanded = computed(() => this._pattern.expanded()); + + /** Whether the tab is selected. */ + readonly selected = computed(() => this._pattern.selected()); + /** The Tab UIPattern. */ readonly _pattern: TabPattern = new TabPattern({ ...this, @@ -301,8 +310,8 @@ export class Tab implements HasElement, OnInit, OnDestroy { 'role': 'tabpanel', 'class': 'ng-tabpanel', '[attr.id]': '_pattern.id()', - '[attr.tabindex]': '_pattern.tabindex()', - '[attr.inert]': '_pattern.hidden() ? true : null', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.inert]': '!visible() ? true : null', '[attr.aria-labelledby]': '_pattern.labelledBy()', }, hostDirectives: [ @@ -328,6 +337,9 @@ export class TabPanel implements OnInit, OnDestroy { /** A local unique identifier for the tabpanel. */ readonly value = input.required(); + /** Whether the tab panel is visible. */ + readonly visible = computed(() => !this._pattern.hidden()); + /** The TabPanel UIPattern. */ readonly _pattern: TabPanelPattern = new TabPanelPattern({ ...this, @@ -336,7 +348,7 @@ export class TabPanel implements OnInit, OnDestroy { }); constructor() { - afterRenderEffect(() => this._deferredContentAware.contentVisible.set(!this._pattern.hidden())); + afterRenderEffect(() => this._deferredContentAware.contentVisible.set(this.visible())); } ngOnInit() { diff --git a/src/aria/toolbar/toolbar.ts b/src/aria/toolbar/toolbar.ts index 8df53db3f665..9cac1f9f0fb8 100644 --- a/src/aria/toolbar/toolbar.ts +++ b/src/aria/toolbar/toolbar.ts @@ -169,8 +169,8 @@ export class Toolbar { exportAs: 'ngToolbarWidget', host: { 'class': 'ng-toolbar-widget', - '[attr.data-active]': '_pattern.active()', - '[attr.tabindex]': '_pattern.tabindex()', + '[attr.data-active]': 'active()', + '[attr.tabindex]': '_pattern.tabIndex()', '[attr.inert]': 'hardDisabled() ? true : null', '[attr.disabled]': 'hardDisabled() ? true : null', '[attr.aria-disabled]': '_pattern.disabled()', @@ -202,6 +202,21 @@ export class ToolbarWidget implements OnInit, OnDestroy { /** Whether the widget is 'hard' disabled, which is different from `aria-disabled`. A hard disabled widget cannot receive focus. */ readonly hardDisabled = computed(() => this._pattern.disabled() && !this._toolbar.softDisabled()); + /** The optional ToolbarWidgetGroup this widget belongs to. */ + readonly _group = inject(ToolbarWidgetGroup, {optional: true}); + + /** The value associated with the widget. */ + readonly value = input.required(); + + /** Whether the widget is currently the active one (focused). */ + readonly active = computed(() => this._pattern.active()); + + /** Whether the widget is selected (only relevant in a selection group). */ + readonly selected = () => this._pattern.selected(); + + readonly group: SignalLike, V> | undefined> = + () => this._group?._pattern; + /** The ToolbarWidget UIPattern. */ readonly _pattern = new ToolbarWidgetPattern({ ...this, diff --git a/src/aria/tree/tree.ts b/src/aria/tree/tree.ts index 098473b0b952..513ac699734f 100644 --- a/src/aria/tree/tree.ts +++ b/src/aria/tree/tree.ts @@ -220,14 +220,14 @@ export class Tree { exportAs: 'ngTreeItem', host: { 'class': 'ng-treeitem', - '[attr.data-active]': '_pattern.active()', + '[attr.data-active]': 'active()', 'role': 'treeitem', '[id]': '_pattern.id()', - '[attr.aria-expanded]': '_pattern.expandable() ? _pattern.expanded() : null', - '[attr.aria-selected]': '_pattern.selected()', + '[attr.aria-expanded]': 'expanded()', + '[attr.aria-selected]': 'selected()', '[attr.aria-current]': '_pattern.current()', '[attr.aria-disabled]': '_pattern.disabled()', - '[attr.aria-level]': '_pattern.level()', + '[attr.aria-level]': 'level()', '[attr.aria-setsize]': '_pattern.setsize()', '[attr.aria-posinset]': '_pattern.posinset()', '[attr.tabindex]': '_pattern.tabindex()', @@ -272,6 +272,23 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr return (this.parent() as TreeItemGroup).ownedBy().tree(); }); + /** Whether the item is active. */ + readonly active = computed(() => this._pattern.active()); + + /** Whether this item is currently expanded, returning null if not expandable. */ + readonly expanded = computed(() => + this._pattern.expandable() ? this._pattern.expanded() : null, + ); + + /** The level of the current item in a tree. */ + readonly level = computed(() => this._pattern.level()); + + /** Whether the item is selected. */ + readonly selected = computed(() => this._pattern.selected()); + + /** Whether this item is visible due to all of its parents being expanded. */ + readonly visible = computed(() => this._pattern.visible()); + /** The UI pattern for this item. */ _pattern: TreeItemPattern;