Spec-Zone.ru › Angular

Список выбора

Шаблон списка выбора Справочник API списка выбора

Обзор

Директива, отображающая список вариантов, из которого пользователи могут выбирать, с поддержкой навигации с клавиатуры, выбора одного или нескольких вариантов и программ чтения с экрана.

import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
<div class="listbox-container">
  <div ngListbox [value]="['Option 1']">
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.listbox-container {
  width: 200px;
  height: 11rem;
  padding: 0.5rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
<div class="material-listbox">
  <div ngListbox>
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.material-listbox {
  width: 200px;
  height: 13rem;
  padding: 0.5rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
<div class="retro-listbox">
  <div ngListbox>
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
}

.retro-listbox {
  width: 200px;
  height: 11rem;
  padding: 0.5rem;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}

Использование

Список выбора — это базовая директива, используемая в шаблонах Select, Multiselect и Autocomplete. Для большинства задач, связанных с выпадающими списками, используйте описанные шаблоны.

Используйте список выбора напрямую в следующих случаях:

  • Создание пользовательских компонентов выбора — для создания специализированных интерфейсов с особым поведением
  • Видимые списки вариантов — для отображения вариантов непосредственно на странице, а не в выпадающем списке
  • Пользовательские сценарии интеграции — для интеграции с уникальными требованиями к всплывающим элементам или макету

Не используйте список выбора в следующих случаях:

  • Нужны меню навигации — используйте директиву Menu для действий и команд

Возможности

Список выбора Angular обеспечивает полностью доступную реализацию списка со следующими возможностями:

  • Навигация с клавиатуры — переход между вариантами с помощью клавиш со стрелками, выбор клавишей Enter или пробелом
  • Поддержка программ чтения с экрана — встроенные атрибуты ARIA, включая role="listbox"
  • Выбор одного или нескольких вариантов — атрибут multi задаёт режим выбора
  • Горизонтальная или вертикальная ориентация — атрибут orientation задаёт направление расположения элементов
  • Поиск по мере ввода — вводите символы, чтобы перейти к подходящим вариантам
  • Реактивность на основе сигналов — реактивное управление состоянием с помощью сигналов Angular

Примеры

Базовый список выбора

Иногда приложениям нужны списки вариантов, которые видны непосредственно на странице, а не скрыты в выпадающем списке. Отдельный список выбора обеспечивает навигацию с клавиатуры и выбор элементов в таких видимых списках.

import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
<div class="listbox-container">
  <div ngListbox [value]="['Option 1']">
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>

Сигнал модели value обеспечивает двустороннюю привязку к выбранным элементам. В режиме selectionMode="explicit" пользователи нажимают пробел или Enter, чтобы выбрать варианты. О шаблонах выпадающих списков, объединяющих список выбора с комбинированным полем и позиционированием наложения, см. в разделе Select.

Горизонтальный список выбора

Иногда списки удобнее отображать горизонтально, например в интерфейсах, похожих на панели инструментов, или при выборе вариантов в виде вкладок. Атрибут orientation меняет и расположение элементов, и направление навигации с клавиатуры.

import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div ngListbox aria-label="Amenities" orientation="horizontal" selectionMode="explicit" multi>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  font-size: 0.8rem;
  font-family: var(--inter-font);
}

[ngListbox] {
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
}

[ngOption] {
  cursor: pointer;
  border-radius: 1rem;
  padding: 0.3rem 1rem;
  color: var(--hot-pink);
  border: 1px solid var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:focus {
  outline: 2px solid var(--hot-pink);
  outline-offset: 2px;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--hot-pink);
}
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
  ngListbox
  class="material-listbox"
  aria-label="Amenities"
  orientation="horizontal"
  selectionMode="explicit"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >check</span
      >
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  font-size: 0.8rem;
  font-family: var(--inter-font);
}

[ngListbox] {
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  border-radius: 0.3rem;
  padding: 0.3rem 0.5rem;
  color: var(--hot-pink);
  border: 1px solid var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:focus {
  outline: 2px solid var(--hot-pink);
  outline-offset: 2px;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--hot-pink);
}

.check-icon {
  width: 0;
  font-size: 1.25rem;
  overflow: hidden;
  transition:
    width 0.2s ease-in-out,
    padding-right 0.2s ease-in-out;
}

[ngOption][aria-selected='true'] .check-icon {
  width: 1.5rem;
  padding-right: 0.2rem;
}
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
  ngListbox
  class="retro-listbox"
  aria-label="Amenities"
  orientation="horizontal"
  selectionMode="explicit"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >check</span
      >
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow-small:
    2px 0px 0px 0px var(--tertiary-contrast), 0px 2px 0px 0px var(--tertiary-contrast),
    -2px 0px 0px 0px var(--tertiary-contrast), 0px -2px 0px 0px var(--tertiary-contrast);
}

.retro-listbox {
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  padding: 0.5rem;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.3rem 0.5rem;
  font-size: 0.6rem;
  box-shadow: var(--retro-flat-shadow-small);
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.check-icon {
  width: 0;
  font-size: 0.9rem;
  overflow: hidden;
  transition: width 0.2s ease-in-out;
}

[ngOption][aria-selected='true'] .check-icon {
  width: 1.2rem;
}

В режиме orientation="horizontal" клавиши со стрелками влево и вправо перемещают фокус между вариантами вместо клавиш вверх и вниз. Список выбора автоматически учитывает языки с письмом справа налево (RTL), меняя направление навигации на противоположное.

Режимы выбора

Список выбора поддерживает два режима, определяющих, когда элементы становятся выбранными.

Режим 'follow' автоматически выбирает элемент, на котором находится фокус, что ускоряет взаимодействие при частом изменении выбора. В режиме 'explicit' для подтверждения выбора нужно нажать пробел или Enter, что предотвращает случайные изменения при навигации. В шаблонах выпадающих списков для выбора одного варианта обычно используется режим 'follow'.

Явный

import {Component} from '@angular/core';
import {Listbox, Option} from '@angular/aria/listbox';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
  ngListbox
  aria-label="Amenities_explicit"
  orientation="horizontal"
  selectionMode="explicit"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>

Следование за фокусом

import {Component} from '@angular/core';
import {Listbox, Option} from '@angular/aria/listbox';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
  ngListbox
  aria-label="Amenities_explicit"
  orientation="horizontal"
  selectionMode="follow"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
Режим Описание
'follow' Автоматически выбирает элемент, на котором находится фокус, что ускоряет взаимодействие при частом изменении выбора
'explicit' Для подтверждения выбора нужно нажать пробел или Enter, что предотвращает случайные изменения при навигации

СОВЕТ: В шаблонах выпадающих списков для выбора одного варианта обычно используется режим 'follow'.

Тестирование

Angular Aria предоставляет компонентные стенды для тестирования компонентов списка выбора. Вот пример использования стендов в тесте компонента:

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ListboxHarness} from '@angular/aria/listbox/testing';
import {MyListboxComponent} from './my-listbox'; // Your component

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

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

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

  it('should allow selecting options', async () => {
    const listbox = await loader.getHarness(ListboxHarness);

    // Verify listbox properties
    expect(await listbox.isMulti()).toBe(true);

    // Get all options
    const options = await listbox.getOptions();
    expect(options.length).toBe(2);

    // Click an option
    await options[0].click();

    // Verify option is selected
    expect(await options[0].isSelected()).toBe(true);

    // Filter options by text
    const bananaOption = await listbox.getOptions({text: 'Banana'});
    expect(bananaOption.length).toBe(1);
  });
});

API

Директива списка выбора

Директива ngListbox создаёт доступный список выбираемых вариантов.

Входные параметры

Свойство Тип Значение по умолчанию Описание
id string auto Уникальный идентификатор списка выбора
multi boolean false Включает множественный выбор
orientation 'vertical' | 'horizontal' 'vertical' Направление расположения элементов списка
wrap boolean true Определяет, зацикливается ли фокус у краёв списка
selectionMode 'follow' | 'explicit' 'follow' Способ запуска выбора
focusMode 'roving' | 'activedescendant' 'roving' Стратегия управления фокусом
softDisabled boolean true Определяет, можно ли перемещать фокус на отключённые элементы
disabled boolean false Отключает весь список выбора
readonly boolean false Переводит список выбора в режим «только для чтения»
typeaheadDelay number 500 Количество миллисекунд до сброса поиска по мере ввода

Модель

Свойство Тип Описание
value V[] Массив выбранных значений с двусторонней привязкой

Сигналы

Свойство Тип Описание
value Signal<V[]> Текущие выбранные значения в виде сигнала

Методы

Метод Параметры Описание
scrollActiveItemIntoView options?: ScrollIntoViewOptions Прокручивает страницу так, чтобы активный элемент оказался в области просмотра
gotoFirst нет Переходит к первому элементу списка выбора

Директива варианта

Директива ngOption помечает элемент внутри списка выбора.

Входные параметры

Свойство Тип Значение по умолчанию Описание
id string auto Уникальный идентификатор варианта
value V - Значение, связанное с этим вариантом (обязательно)
label string - Необязательная подпись для программ чтения с экрана
disabled boolean false Определяет, отключён ли этот вариант

Сигналы

Свойство Тип Описание
selected Signal<boolean> Определяет, выбран ли этот вариант
active Signal<boolean> Определяет, находится ли этот вариант в фокусе

Связанные шаблоны

Этот список выбора используется в описанных шаблонах выпадающих списков:

  • Select — шаблон выпадающего списка с выбором одного варианта, использующий комбинированное поле только для чтения и список выбора
  • Multiselect — шаблон выпадающего списка с выбором нескольких вариантов, использующий комбинированное поле только для чтения и список выбора с multi
  • Autocomplete — фильтруемый шаблон выпадающего списка, использующий комбинированное поле и список выбора

Полные шаблоны выпадающих списков с триггером, всплывающим окном и позиционированием наложения описаны в соответствующих руководствах; используйте их вместо списка выбора в отдельности.

Шаблон ARIA для списка выбора Справочник API списка выбора

Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/aria/listbox

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API