심화 가이드
접근성 지원

트리

개요

트리는 계층형 데이터를 표시합니다. 항목을 펼치면 자식 항목이 나타나고 접으면 숨겨집니다. 사용자는 방향키로 탐색하고 노드를 펼치거나 접을 수 있으며, 탐색이나 데이터 선택 용도에 따라 항목을 선택할 수도 있습니다.

사용법

트리는 사용자가 중첩된 구조를 탐색해야 하는 계층형 데이터를 표시할 때 적합합니다.

트리를 사용하기 좋은 경우:

  • 파일 시스템 탐색 기능을 만들 때
  • 폴더와 문서의 계층 구조를 표시할 때
  • 중첩된 메뉴 구조를 만들 때
  • 조직도를 표시할 때
  • 계층형 데이터를 탐색할 때
  • 중첩된 섹션이 있는 사이트 탐색 기능을 구현할 때

트리를 피해야 하는 경우:

  • 평면적인 목록을 표시할 때(리스트박스를 사용하세요)
  • 데이터 표를 표시할 때(그리드를 사용하세요)
  • 간단한 드롭다운을 만들 때(선택을 사용하세요)
  • 이동 경로 탐색을 만들 때(이동 경로 패턴을 사용하세요)

주요 기능

  • 계층형 탐색: 펼치기와 접기 기능이 있는 중첩 트리 구조를 제공합니다.
  • 선택 모드: 명시적으로 선택하거나 포커스에 따라 선택하는 단일/다중 선택을 지원합니다.
  • 포커스에 따른 선택: 포커스가 바뀔 때 자동으로 선택하도록 설정할 수 있습니다.
  • 키보드 탐색: 방향키, Home, End, 문자 입력 검색을 지원합니다.
  • 펼치기/접기: 오른쪽/왼쪽 방향키나 Enter로 부모 노드의 펼침 상태를 전환합니다.
  • 비활성화된 항목: 특정 노드를 비활성화하고 포커스를 관리합니다.
  • 포커스 모드: roving tabindex 또는 activedescendant 포커스 전략을 사용합니다.
  • RTL 지원: 오른쪽에서 왼쪽으로 쓰는 언어의 탐색을 지원합니다.

예제

탐색 트리

항목을 클릭하면 선택하는 대신 작업을 실행하는 탐색 용도로 트리를 사용할 수 있습니다.

[nav]="true"로 설정하면 탐색 모드가 활성화됩니다. 이 모드에서는 선택 상태 대신 aria-current로 현재 페이지를 나타냅니다.

단일 선택

사용자가 트리에서 항목을 하나만 골라야 한다면 단일 선택을 활성화하세요.

단일 선택에는 [multi]="false"(기본값)를 유지하세요. 사용자는 Space를 눌러 포커스가 있는 항목을 선택합니다.

다중 선택

사용자가 트리에서 여러 항목을 선택하도록 할 수 있습니다.

트리에 [multi]="true"를 설정하세요. 사용자는 Space로 개별 항목을 선택하거나 Shift+방향키로 범위를 선택합니다.

포커스에 따라 선택하기

포커스에 따라 선택하도록 설정하면 포커스가 있는 항목이 자동으로 선택됩니다. 탐색 용도로 사용할 때 조작을 단순화할 수 있습니다.

트리에 [selectionMode]="'follow'"를 설정하세요. 사용자가 방향키로 탐색할 때 선택 상태가 자동으로 변경됩니다.

비활성화된 트리 항목

특정 트리 노드를 비활성화해서 조작을 막을 수 있습니다. 비활성화된 항목이 포커스를 받을 수 있는지도 제어할 수 있습니다.

트리의 [softDisabled]="true"이면 비활성화된 항목도 포커스를 받을 수 있지만 활성화하거나 선택할 수는 없습니다. [softDisabled]="false"이면 키보드 탐색에서 비활성화된 항목을 건너뜁니다.

테스트

Angular Aria는 트리 컴포넌트를 테스트할 수 있는 컴포넌트 하네스를 제공합니다. 컴포넌트 테스트에서 하네스를 사용하는 예제는 다음과 같습니다.

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {TreeHarness} from '@angular/aria/tree/testing';
import {MyTreeComponent} from './my-tree'; // 테스트할 컴포넌트

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

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

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

  it('should navigate and expand tree items', async () => {
    const tree = await loader.getHarness(TreeHarness);

    // 최상위 구조를 나타내는 값을 가져옵니다.
    expect(await tree.getTreeStructure()).toEqual({
      children: [{text: 'public'}, {text: 'src'}, {text: 'package.json'}],
    });

    // 현재 표시된 모든 항목을 가져옵니다.
    const items = await tree.getItems();
    expect(items.length).toBe(3);

    // 첫 번째 항목('public')을 펼칩니다.
    expect(await items[0].isExpanded()).toBe(false);
    await items[0].click();
    expect(await items[0].isExpanded()).toBe(true);

    // 펼친 뒤 트리 구조가 변경되었는지 확인합니다.
    expect(await tree.getTreeStructure()).toEqual({
      children: [
        {
          text: 'public',
          children: [{text: 'index.html'}, {text: 'styles.css'}],
        },
        {text: 'src'},
        {text: 'package.json'},
      ],
    });
  });
});

API 레퍼런스

자세한 API 문서는 다음 API 레퍼런스를 참고하세요.