diff --git a/goldens/aria/private/index.api.md b/goldens/aria/private/index.api.md index a605f4808bd1..36f65f63c218 100644 --- a/goldens/aria/private/index.api.md +++ b/goldens/aria/private/index.api.md @@ -701,6 +701,7 @@ export class ToolbarWidgetGroupPattern, V> { // @public export interface ToolbarWidgetInputs extends Omit, 'searchTerm' | 'index' | 'selectable'> { group: SignalLike, V> | undefined>; + selectable?: SignalLike; toolbar: SignalLike>; } diff --git a/goldens/aria/toolbar/index.api.md b/goldens/aria/toolbar/index.api.md index 214740b50357..2fa3a0b1c411 100644 --- a/goldens/aria/toolbar/index.api.md +++ b/goldens/aria/toolbar/index.api.md @@ -48,11 +48,12 @@ export class ToolbarWidget implements OnInit, OnDestroy { // (undocumented) ngOnInit(): void; readonly _pattern: ToolbarWidgetPattern; + readonly selectable: _angular_core.InputSignalWithTransform; readonly selected: () => boolean; readonly _toolbarPattern: _angular_core.Signal>; readonly value: _angular_core.InputSignal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngToolbarWidget]", ["ngToolbarWidget"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngToolbarWidget]", ["ngToolbarWidget"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } diff --git a/src/aria/private/toolbar/toolbar-widget.ts b/src/aria/private/toolbar/toolbar-widget.ts index 4239de8b2727..99ec64fccda5 100644 --- a/src/aria/private/toolbar/toolbar-widget.ts +++ b/src/aria/private/toolbar/toolbar-widget.ts @@ -21,6 +21,9 @@ export interface ToolbarWidgetInputs extends Omit< /** A reference to the parent widget group. */ group: SignalLike, V> | undefined>; + + /** Whether the widget is selectable. Defaults to true. */ + selectable?: SignalLike; } export class ToolbarWidgetPattern implements ListItem { @@ -49,7 +52,7 @@ export class ToolbarWidgetPattern implements ListItem { readonly value = () => this.inputs.value(); /** Whether the widget is selectable. */ - readonly selectable = () => true; // Unused because toolbar does not support selection. + readonly selectable = () => (this.inputs.selectable ? this.inputs.selectable() : true); /** The position of the widget within the toolbar. */ readonly index = computed(() => this.toolbar().inputs.items().indexOf(this) ?? -1); diff --git a/src/aria/private/toolbar/toolbar.spec.ts b/src/aria/private/toolbar/toolbar.spec.ts index b62ec5d01fbc..db5f4d87750f 100644 --- a/src/aria/private/toolbar/toolbar.spec.ts +++ b/src/aria/private/toolbar/toolbar.spec.ts @@ -55,6 +55,27 @@ function clickItem(item: ToolbarWidgetPattern, mods?: ModifierKeys) { } as unknown as PointerEvent; } +function keyEvent(key: string, target?: Element): KeyboardEvent { + const event = createKeyboardEvent('keydown', 0, key); + if (target) { + Object.defineProperty(event, 'target', {value: target, configurable: true}); + Object.defineProperty(event, 'composedPath', {value: () => [target], configurable: true}); + } + return event; +} + +function pointerdownEvent(target: Element) { + let defaultPrevented = false; + return { + target, + composedPath: () => [target], + defaultPrevented: () => defaultPrevented, + preventDefault: () => { + defaultPrevented = true; + }, + } as unknown as PointerEvent & {defaultPrevented: () => boolean}; +} + function getToolbarPattern( inputs: Partial<{ [K in keyof TestInputs]: TestInputs[K] extends WritableSignalLike ? T : never; @@ -96,8 +117,12 @@ function getWidgetPattern( value: string, toolbar: ToolbarPattern, group?: ToolbarWidgetGroupPattern, string>, + options?: { + element?: HTMLElement; + selectable?: SignalLike; + }, ): TestWidget { - const element = signal(document.createElement('button')); + const element = signal(options?.element ?? document.createElement('button')); const widget = new ToolbarWidgetPattern({ id: signal(`widget-${value}`), element, @@ -105,6 +130,7 @@ function getWidgetPattern( value: signal(value), group: signal(group), toolbar: signal(toolbar), + selectable: options?.selectable, }); return widget as TestWidget; } @@ -1296,22 +1322,28 @@ describe('Toolbar Pattern', () => { }); describe('Selection', () => { - it('should toggle the active item on Enter (selection)', () => { + it('should toggle the active item in a group on Enter (selection)', () => { const {toolbar} = getPatterns(); - expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + // Navigate to item 2 (in group 0) + toolbar.onKeydown(right()); + toolbar.onKeydown(right()); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); toolbar.onKeydown(enter()); - expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + expect(getItem(toolbar, 'item 2').selected()).toBeTrue(); toolbar.onKeydown(enter()); - expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); }); - it('should toggle the active item on Space (selection)', () => { + it('should toggle the active item in a group on Space (selection)', () => { const {toolbar} = getPatterns(); - expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + // Navigate to item 2 (in group 0) + toolbar.onKeydown(right()); + toolbar.onKeydown(right()); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); toolbar.onKeydown(space()); - expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + expect(getItem(toolbar, 'item 2').selected()).toBeTrue(); toolbar.onKeydown(space()); - expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); }); it('should toggle the active item on click (selection)', () => { @@ -1323,19 +1355,18 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); }); - it('should be able to select multiple items in the toolbar (selection)', () => { - const {toolbar} = getPatterns(); + it('should be able to select multiple items via click (selection)', () => { + const {toolbar, items} = getPatterns(); expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); // Select first item - toolbar.onKeydown(enter()); + toolbar.onClick(clickItem(items[0])); expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); - // Navigate to and select second item - toolbar.onKeydown(right()); - toolbar.onKeydown(space()); + // Select second item + toolbar.onClick(clickItem(items[1])); expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); expect(getItem(toolbar, 'item 1').selected()).toBeTrue(); }); @@ -1363,12 +1394,8 @@ describe('Toolbar Pattern', () => { const {toolbar, items} = getPatterns(); items[1].inputs.disabled.set(true); - // Navigate to disabled item - toolbar.onKeydown(right()); - expect(toolbar.activeItem()?.value()).toBe('item 1'); - - // Try to select disabled item - toolbar.onKeydown(enter()); + // Try to click disabled item + toolbar.onClick(clickItem(items[1])); expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); }); @@ -1395,31 +1422,130 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); // Should reset to item 0 }); - it('should NOT set default state if keyboard interacted', () => { + it('should not set default state if already interacted', () => { const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[0]); - toolbar.onKeydown(right()); // Interaction (ArrowRight moves to item 1) - + toolbar.inputs.activeItem.set(items[1]); // Set to item 1 + toolbar.onKeydown(right()); // Mark interacted toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 1'); // Should stay on item 1 (interacted) + expect(toolbar.activeItem()?.value()).toBe('item 2'); // Retains navigation target }); + }); - it('should NOT set default state if pointer interacted', () => { - const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[1]); - toolbar.onPointerdown(clickItem(items[1])); // Interaction + describe('Form Controls & Composite Widgets Support (Generic Handling)', () => { + describe('Pointerdown event handling', () => { + it('should mark interacted without preventDefault', () => { + const {toolbar} = getPatterns(); + const divEl = document.createElement('div'); + const event = pointerdownEvent(divEl); + toolbar.onPointerdown(event); + expect(event.defaultPrevented()).toBeFalse(); + expect(toolbar.hasBeenInteracted()).toBeTrue(); + }); + }); - toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 1'); // Should stay on item 1 + describe('Standalone widget keyboard navigation (horizontal)', () => { + let toolbar: ToolbarPattern; + let selectEl: HTMLSelectElement; + let selectWidget: TestWidget; + let inputEl: HTMLInputElement; + let inputWidget: TestWidget; + let nextWidget: TestWidget; + + beforeEach(() => { + selectEl = document.createElement('select'); + inputEl = document.createElement('input'); + inputEl.type = 'number'; + + const items = signal([]); + const setup = getToolbarPattern({orientation: 'horizontal'}, items); + toolbar = setup.toolbar; + + selectWidget = getWidgetPattern('select-widget', toolbar, undefined, { + element: selectEl, + }); + inputWidget = getWidgetPattern('number-widget', toolbar, undefined, { + element: inputEl, + }); + nextWidget = getWidgetPattern('button-next', toolbar); + + items.set([selectWidget, inputWidget, nextWidget]); + toolbar.setDefaultState(); + }); + + it('should NOT move toolbar focus on ArrowUp / ArrowDown for standalone widgets', () => { + expect(toolbar.activeItem()?.value()).toBe('select-widget'); + + toolbar.onKeydown(keyEvent('ArrowDown', selectEl)); + expect(toolbar.activeItem()?.value()).toBe('select-widget'); + + toolbar.onKeydown(keyEvent('ArrowUp', selectEl)); + expect(toolbar.activeItem()?.value()).toBe('select-widget'); + }); + + it('should move toolbar focus on ArrowRight / ArrowLeft across standalone widgets', () => { + toolbar.onKeydown(keyEvent('ArrowRight', selectEl)); + expect(toolbar.activeItem()?.value()).toBe('number-widget'); + + toolbar.onKeydown(keyEvent('ArrowRight', inputEl)); + expect(toolbar.activeItem()?.value()).toBe('button-next'); + + toolbar.onKeydown(keyEvent('ArrowLeft', nextWidget.element())); + expect(toolbar.activeItem()?.value()).toBe('number-widget'); + }); + + it('should navigate to first/last on Home/End', () => { + toolbar.onKeydown(keyEvent('End', selectEl)); + expect(toolbar.activeItem()?.value()).toBe('button-next'); + + toolbar.onKeydown(keyEvent('Home', nextWidget.element())); + expect(toolbar.activeItem()?.value()).toBe('select-widget'); + }); + + it('should NOT select widget when selectable is false', () => { + const nonSelectableWidget = getWidgetPattern('non-sel', toolbar, undefined, { + selectable: signal(false), + }); + expect(nonSelectableWidget.selectable()).toBeFalse(); + + toolbar.onClick(clickItem(nonSelectableWidget)); + expect(nonSelectableWidget.selected()).toBeFalse(); + expect(toolbar.listBehavior.inputs.value()).toEqual([]); + }); }); - it('should NOT set default state if focus-in occurred', () => { - const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[1]); - toolbar.onFocusIn(); // Interaction + describe('Non-selectable widgets inside widget group', () => { + it('should NOT toggle on Space or Enter when selectable is false in group', () => { + const items = signal([]); + const setup = getToolbarPattern({orientation: 'horizontal'}, items); + const toolbar = setup.toolbar; + + const group = new ToolbarWidgetGroupPattern({ + disabled: signal(false), + multi: signal(false), + toolbar: () => toolbar, + items: signal([]), + }); - toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 1'); // Should stay on item 1 + const widget1 = getWidgetPattern('w1', toolbar, group, {selectable: signal(false)}); + const widget2 = getWidgetPattern('w2', toolbar, group, {selectable: signal(true)}); + (group.inputs.items as any).set([widget1, widget2]); + items.set([widget1, widget2]); + toolbar.setDefaultState(); + + expect(toolbar.activeItem()?.value()).toBe('w1'); + toolbar.onKeydown(space()); + expect(widget1.selected()).toBeFalse(); + + toolbar.onKeydown(enter()); + expect(widget1.selected()).toBeFalse(); + + // Navigate to selectable widget2 in group + toolbar.onKeydown(right()); + expect(toolbar.activeItem()?.value()).toBe('w2'); + + toolbar.onKeydown(enter()); + expect(widget2.selected()).toBeTrue(); + }); }); }); }); diff --git a/src/aria/private/toolbar/toolbar.ts b/src/aria/private/toolbar/toolbar.ts index 1fab0665c4a2..3889ce5b2fb4 100644 --- a/src/aria/private/toolbar/toolbar.ts +++ b/src/aria/private/toolbar/toolbar.ts @@ -82,16 +82,25 @@ export class ToolbarPattern { /** The keydown event manager for the toolbar. */ private readonly _keydown = computed(() => { const manager = new KeyboardEventManager(); + const activeItem = this.inputs.activeItem(); - return manager + manager .on(this._nextKey, () => this.listBehavior.next(), {ignoreRepeat: false}) .on(this._prevKey, () => this.listBehavior.prev(), {ignoreRepeat: false}) - .on(this._altNextKey, () => this._groupNext(), {ignoreRepeat: false}) - .on(this._altPrevKey, () => this._groupPrev(), {ignoreRepeat: false}) - .on(' ', () => this.select()) - .on('Enter', () => this.select()) .on('Home', () => this.listBehavior.first()) .on('End', () => this.listBehavior.last()); + + if (activeItem?.group()) { + manager + .on(this._altNextKey, () => this._groupNext(), {ignoreRepeat: false}) + .on(this._altPrevKey, () => this._groupPrev(), {ignoreRepeat: false}); + } + + if (activeItem?.selectable() && activeItem?.group()) { + manager.on(' ', () => this.select()).on('Enter', () => this.select()); + } + + return manager; }); /** Navigates to the next widget in a widget group. */ @@ -144,15 +153,25 @@ export class ToolbarPattern { if (item) { this.listBehavior.goto(item); - this.select(); + if (item.selectable()) { + this.select(); + } } } select() { - const group = this.inputs.activeItem()?.group(); + const activeItem = this.inputs.activeItem(); + if (!activeItem || !activeItem.selectable()) { + return; + } + const group = activeItem.group(); if (!group?.multi()) { - group?.inputs.items().forEach(i => this.listBehavior.deselect(i)); + group?.inputs.items().forEach(i => { + if (i !== activeItem) { + this.listBehavior.deselect(i); + } + }); } this.listBehavior.toggle(); @@ -193,7 +212,6 @@ export class ToolbarPattern { onPointerdown(event: PointerEvent) { this.hasBeenInteracted.set(true); - event.preventDefault(); } onFocusIn() { @@ -202,7 +220,7 @@ export class ToolbarPattern { /** Handles click events for the toolbar. */ onClick(event: MouseEvent) { - if (this.disabled() || (event as PointerEvent).pointerType === '') return; + if (this.disabled()) return; this._goto(event); } diff --git a/src/aria/toolbar/toolbar-widget.ts b/src/aria/toolbar/toolbar-widget.ts index 4a626f3f57b4..68565487a1ad 100644 --- a/src/aria/toolbar/toolbar-widget.ts +++ b/src/aria/toolbar/toolbar-widget.ts @@ -84,6 +84,9 @@ export class ToolbarWidget implements OnInit, OnDestroy { /** The value associated with the widget. */ readonly value = input.required(); + /** Whether the widget is selectable. Complex widgets (e.g. select, input, combobox) can set this to false. */ + readonly selectable = input(true, {transform: booleanAttribute}); + /** Whether the widget is currently active (focused). */ readonly active = computed(() => this._pattern.active()); @@ -101,6 +104,7 @@ export class ToolbarWidget implements OnInit, OnDestroy { toolbar: this._toolbarPattern, id: this.id, value: this.value, + selectable: this.selectable, element: () => this.element, }); diff --git a/src/aria/toolbar/toolbar.spec.ts b/src/aria/toolbar/toolbar.spec.ts index 5ffecc259df2..8d4ea2dbfa3a 100644 --- a/src/aria/toolbar/toolbar.spec.ts +++ b/src/aria/toolbar/toolbar.spec.ts @@ -597,22 +597,24 @@ describe('Toolbar', () => { describe('Selection', () => { beforeEach(async () => await setupToolbar()); - it('should toggle the active item on Enter', async () => { - const item0 = getWidgetEl('item 0')!; - await click(item0); + it('should toggle the active item in a group on Enter', async () => { + const item2 = getWidgetEl('item 2')!; + await click(item2); + expect(item2.getAttribute('aria-pressed')).toBe('true'); await keydown('Enter'); - expect(item0.getAttribute('aria-pressed')).toBe('false'); + expect(item2.getAttribute('aria-pressed')).toBe('false'); await keydown('Enter'); - expect(item0.getAttribute('aria-pressed')).toBe('true'); + expect(item2.getAttribute('aria-pressed')).toBe('true'); }); - it('should toggle the active item on Space', async () => { - const item0 = getWidgetEl('item 0')!; - await click(item0); + it('should toggle the active item in a group on Space', async () => { + const item2 = getWidgetEl('item 2')!; + await click(item2); + expect(item2.getAttribute('aria-pressed')).toBe('true'); await keydown(' '); - expect(item0.getAttribute('aria-pressed')).toBe('false'); + expect(item2.getAttribute('aria-pressed')).toBe('false'); await keydown(' '); - expect(item0.getAttribute('aria-pressed')).toBe('true'); + expect(item2.getAttribute('aria-pressed')).toBe('true'); }); it('should toggle the active item on click', async () => { @@ -743,6 +745,87 @@ describe('Toolbar', () => { ); }); }); + + describe('Form controls and embedded widgets (Issue #33684)', () => { + let formFixture: ComponentFixture; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [ToolbarWithFormControlsExample], + providers: [provideFakeDirectionality('ltr')], + }); + formFixture = TestBed.createComponent(ToolbarWithFormControlsExample); + fixture = formFixture as any; + await formFixture.whenStable(); + }); + + it('should not move toolbar focus on ArrowUp/ArrowDown on select in horizontal toolbar', async () => { + const selectEl = formFixture.debugElement.query(By.css('select')) + .nativeElement as HTMLElement; + await click(selectEl); + expect(document.activeElement).toBe(selectEl); + + await down(selectEl); + expect(document.activeElement).toBe(selectEl); + + await up(selectEl); + expect(document.activeElement).toBe(selectEl); + }); + + it('should navigate across toolbar on ArrowRight / ArrowLeft from select', async () => { + const selectEl = formFixture.debugElement.query(By.css('select')) + .nativeElement as HTMLElement; + const inputEl = formFixture.debugElement.query(By.css('input')).nativeElement as HTMLElement; + const buttons = formFixture.debugElement + .queryAll(By.css('button')) + .map(de => de.nativeElement as HTMLElement); + + await click(selectEl); + expect(document.activeElement).toBe(selectEl); + + await right(selectEl); + expect(document.activeElement).toBe(inputEl); + + await right(inputEl); + expect(document.activeElement).toBe(buttons[1]); // Italic button + + await left(buttons[1]); + expect(document.activeElement).toBe(inputEl); + + await left(inputEl); + expect(document.activeElement).toBe(selectEl); + + await left(selectEl); + expect(document.activeElement).toBe(buttons[0]); // Bold button + }); + + it('should not move toolbar focus on ArrowUp/ArrowDown on number input in horizontal toolbar', async () => { + const inputEl = formFixture.debugElement.query(By.css('input')).nativeElement as HTMLElement; + await click(inputEl); + expect(document.activeElement).toBe(inputEl); + + await down(inputEl); + expect(document.activeElement).toBe(inputEl); + + await up(inputEl); + expect(document.activeElement).toBe(inputEl); + }); + + it('should not mark form control widgets with selectable false as selected upon click', async () => { + const selectEl = formFixture.debugElement.query(By.css('select')) + .nativeElement as HTMLElement; + const inputEl = formFixture.debugElement.query(By.css('input')).nativeElement as HTMLElement; + const toolbarInstance = formFixture.debugElement + .query(By.directive(Toolbar)) + .injector.get(Toolbar); + + await click(selectEl); + expect(toolbarInstance.value()).toEqual([]); + + await click(inputEl); + expect(toolbarInstance.value()).toEqual([]); + }); + }); }); @Component({ @@ -879,3 +962,23 @@ class ToolbarWithDuplicateValues {} changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarGroupOutsideToolbar {} + +@Component({ + template: ` +
+ + + + +
+ `, + imports: [Toolbar, ToolbarWidget], + changeDetection: ChangeDetectionStrategy.Eager, +}) +class ToolbarWithFormControlsExample { + orientation = signal<'vertical' | 'horizontal'>('horizontal'); +} diff --git a/src/components-examples/aria/toolbar/index.ts b/src/components-examples/aria/toolbar/index.ts index 89582a318a90..5fe0a05c6e33 100644 --- a/src/components-examples/aria/toolbar/index.ts +++ b/src/components-examples/aria/toolbar/index.ts @@ -3,3 +3,4 @@ export {ToolbarBasicVerticalExample} from './toolbar-basic-vertical/toolbar-basi export {ToolbarConfigurableExample} from './toolbar-configurable/toolbar-configurable-example'; export {ToolbarRtlExample} from './toolbar-rtl/toolbar-rtl-example'; export {ToolbarHardDisabledExample} from './toolbar-hard-disabled/toolbar-hard-disabled-example'; +export {ToolbarSelectAndComboboxExample} from './toolbar-select-and-combobox/toolbar-select-and-combobox-example'; diff --git a/src/components-examples/aria/toolbar/simple-toolbar.ts b/src/components-examples/aria/toolbar/simple-toolbar.ts index 76094c8cb32a..ed2394f75053 100644 --- a/src/components-examples/aria/toolbar/simple-toolbar.ts +++ b/src/components-examples/aria/toolbar/simple-toolbar.ts @@ -71,6 +71,7 @@ export class SimpleToolbarRadioButton { ngCombobox #combobox="ngCombobox" ngToolbarWidget + [selectable]="false" [(value)]="value" [(expanded)]="popupExpanded" (click)="origin.focus()"> diff --git a/src/components-examples/aria/toolbar/toolbar-select-and-combobox/toolbar-select-and-combobox-example.html b/src/components-examples/aria/toolbar/toolbar-select-and-combobox/toolbar-select-and-combobox-example.html new file mode 100644 index 000000000000..c984c82fb8c0 --- /dev/null +++ b/src/components-examples/aria/toolbar/toolbar-select-and-combobox/toolbar-select-and-combobox-example.html @@ -0,0 +1,80 @@ +
+
+ +
+ + +
+ + + + +
+ + + +
+ + + + + + + + + +
+ + +
+ + + + +
+ + +
+ + + + +
+ + + +
+
+ +
+

Selected Font: {{ selectedFont() }} | Size: {{ fontSize() }}px

+
+
diff --git a/src/components-examples/aria/toolbar/toolbar-select-and-combobox/toolbar-select-and-combobox-example.ts b/src/components-examples/aria/toolbar/toolbar-select-and-combobox/toolbar-select-and-combobox-example.ts new file mode 100644 index 000000000000..4596ba896a57 --- /dev/null +++ b/src/components-examples/aria/toolbar/toolbar-select-and-combobox/toolbar-select-and-combobox-example.ts @@ -0,0 +1,31 @@ +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; +import { + ToolbarCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, +} from '../simple-toolbar'; + +/** @title Toolbar with Select, Combobox, and Spinbutton Example */ +@Component({ + selector: 'toolbar-select-and-combobox-example', + templateUrl: 'toolbar-select-and-combobox-example.html', + styleUrl: '../toolbar-common.css', + imports: [ + FormsModule, + Toolbar, + ToolbarWidget, + ToolbarWidgetGroup, + ToolbarCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ToolbarSelectAndComboboxExample { + readonly selectedFont = signal('sans-serif'); + readonly fontSize = signal(16); +} diff --git a/src/dev-app/aria-toolbar/toolbar-demo.html b/src/dev-app/aria-toolbar/toolbar-demo.html index b6e9553457de..19da7264e4ef 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.html +++ b/src/dev-app/aria-toolbar/toolbar-demo.html @@ -20,4 +20,8 @@

Toolbar RTL

Configurable CDK Toolbar

+
+

Toolbar with Select, Combobox & Spinbutton

+ +
diff --git a/src/dev-app/aria-toolbar/toolbar-demo.ts b/src/dev-app/aria-toolbar/toolbar-demo.ts index 08910d10af16..0ce08b04d1c4 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.ts +++ b/src/dev-app/aria-toolbar/toolbar-demo.ts @@ -13,6 +13,7 @@ import { ToolbarConfigurableExample, ToolbarRtlExample, ToolbarHardDisabledExample, + ToolbarSelectAndComboboxExample, } from '@angular/components-examples/aria/toolbar'; @Component({ @@ -23,6 +24,7 @@ import { ToolbarConfigurableExample, ToolbarRtlExample, ToolbarHardDisabledExample, + ToolbarSelectAndComboboxExample, ], styleUrl: './toolbar-demo.css', encapsulation: ViewEncapsulation.None,