深入指南
Angular Aria

Menu

概述

選單提供一份可供使用者選取的動作或選項清單,通常在點選按鈕或按右鍵時出現。選單支援用方向鍵進行鍵盤導覽,也支援子選單、核取方塊、選項按鈕與停用項目。

用法

選單很適合呈現可供使用者選取的動作或指令清單。

適合使用選單的情況:

  • 建立應用程式指令選單(File、Edit、View)
  • 建立內容選單(按右鍵執行的動作)
  • 顯示下拉式動作清單
  • 實作工具列下拉選單
  • 組織設定或選項

避免使用選單的情況:

  • 建立網站導覽(改用導覽地標)
  • 建立表單選取欄位(改用 Select 元件)
  • 在內容面板之間切換(改用 Tabs)
  • 顯示可收合的內容(改用 Accordion)

功能

  • 鍵盤導覽:用方向鍵、Home/End 與字元搜尋,導覽更有效率
  • 子選單:支援巢狀選單,並自動定位
  • 選單類型:獨立選單、觸發式選單與功能表列
  • 核取方塊與選項按鈕:可切換與可選取的選單項目
  • 停用項目:軟停用或硬停用狀態,並管理焦點
  • 自動關閉行為:可設定選取後是否關閉
  • RTL 支援:支援由右至左語言的導覽

範例

搭配觸發器的選單

將觸發按鈕與選單配成一組,就能建立下拉選單。觸發器負責開啟與關閉選單。

使用者選取項目或按下 Escape 時,選單會自動關閉。

內容選單

當使用者在元素上按右鍵時,內容選單會出現在游標位置。

使用 contextmenu 事件的座標來定位選單。

獨立選單

獨立選單不需要觸發器,會一直顯示在介面中。

獨立選單很適合用在隨時可見的動作清單或導覽上。

停用選單項目

使用 disabled 輸入屬性停用個別選單項目,並用 softDisabled 控制其焦點行為。

當 [softDisabled]="true" 時,停用項目可以取得焦點,但無法啟用。當 [softDisabled]="false" 時,鍵盤導覽會跳過停用項目。

測試

Angular Aria 提供元件測試工具(component harness)來測試選單元件。 以下範例示範如何在元件測試中使用這些測試工具:

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing';
import {MyMenuComponent} from './my-menu'; // Your component

describe('MyMenuComponent', () => {
  let fixture: ComponentFixture<MyMenuComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyMenuComponent],
    });

    fixture = TestBed.createComponent(MyMenuComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should open menu and click item', async () => {
    // Load the menu harness by its trigger text
    const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));

    // Verify initial state
    expect(await menu.isOpen()).toBe(false);

    // Open the menu
    await menu.open();
    expect(await menu.isOpen()).toBe(true);

    // Get items
    const items = await menu.getItems();
    expect(items.length).toBe(3);
    expect(await items[0].getText()).toBe('Item 1');

    // Click first item
    await items[0].click();

    // Menu should close after selection (depending on your implementation)
    expect(await menu.isOpen()).toBe(false);
  });

  it('should interact with submenus', async () => {
    const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
    await menu.open();

    // Get the item that triggers a submenu
    const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'}));
    expect(await subItem.hasSubmenu()).toBe(true);

    // Open submenu
    await subItem.click();
    const submenu = await subItem.getSubmenu();
    expect(submenu).toBeTruthy();
    expect(await submenu!.isOpen()).toBe(true);

    // Interact with submenu items
    const subItems = await submenu!.getItems();
    expect(subItems.length).toBe(1);
  });
});

API 參考

如需詳細的 API 文件,請查看下列 API 參考: