オートコンプリート
概要
ユーザーが入力するにつれてオプションをフィルタリングして提案し、リストから値を見つけて選択するのに役立つ、アクセシブルな入力フィールドです。
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="auto-select"> <div #origin class="autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}.autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 0.25rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--hot-pink); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 0.5rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="auto-select"> <div #origin class="material-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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);}.material-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 3rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--primary); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:focus-within [ngComboboxInput] { outline: 2px solid var(--primary); outline-offset: 2px;}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 2rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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: 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="auto-select"> <div #origin class="retro-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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.6rem; font-family: 'Press Start 2P'; --retro-button-color: #fff; --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-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}.retro-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: #000; z-index: 1;}[ngComboboxInput] { width: 15rem; font-size: 0.6rem; border-radius: 0; font-family: 'Press Start 2P'; word-spacing: -5px; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: #000; border: none; box-shadow: var(--retro-flat-shadow); background-color: var(--retro-button-color);}[ngComboboxInput]::placeholder { color: #000; opacity: 0.7;}[ngComboboxInput]:focus { outline: none; transform: translate(1px, 1px); box-shadow: var(--retro-pressed-shadow);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 20px; padding: 0.5rem; max-height: 11rem; border-radius: 0; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}.no-results { padding: 1rem;}[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;}[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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
使用法
オートコンプリートは、タイピングがスクロールよりも速い大規模なオプションのセットからユーザーが選択する必要がある場合に最適です。次のような場合にオートコンプリートの使用を検討してください:
- オプションリストが長い (20項目以上) - タイピングすることで、ドロップダウンをスクロールするよりも速く選択肢を絞り込めます
- ユーザーが探しているものを知っている - 期待される値の一部を入力できます (州名、製品名、ユーザー名など)
- オプションが予測可能なパターンに従っている - ユーザーが部分一致を推測できます (国コード、メールドメイン、カテゴリーなど)
- スピードが重要である - フォームは、広範なナビゲーションなしで迅速に選択できるというメリットがあります
次のような場合はオートコンプリートを避けてください:
- リストのオプションが10個未満の場合 - 通常のドロップダウンやラジオグループの方が視認性が高いためです
- ユーザーがオプションを閲覧する必要がある場合 - 発見が重要である場合は、すべてのオプションを最初から表示します
- オプションが馴染みのないものである場合 - ユーザーはリストに存在することを知らないものを入力できないためです
機能
Angularのオートコンプリートは、以下の機能を備えた、完全にアクセシブルなコンボボックスの実装を提供します:
- キーボードナビゲーション - 矢印キーでオプションを移動し、Enterで選択、Escapeで閉じます
- スクリーンリーダーのサポート - 支援技術のための組み込みARIA属性
- 3つのフィルターモード - 自動選択、手動選択、またはハイライト表示の動作から選択できます
- シグナルベースのリアクティビティ - Angularシグナルを使用したリアクティブな状態管理
- Popover APIとの統合 - ネイティブのHTML Popover APIを活用し、最適な配置を実現します
- 双方向テキストのサポート - 右から左へ記述する言語(RTL)に自動的に対応します
例
自動選択モード
ユーザーがテキストの一部を入力すると、その入力が利用可能なオプションと一致することの即時確認が期待されます。自動選択モードは、ユーザーが入力するにつれて、フィルタリングされた最初のオプションに一致するように入力値を更新し、必要なキーストロークの数を減らし、検索が正しい方向に向かっていることを即座にフィードバックします。
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="auto-select"> <div #origin class="autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}.autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 0.25rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--hot-pink); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 0.5rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="auto-select"> <div #origin class="material-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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);}.material-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 3rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--primary); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:focus-within [ngComboboxInput] { outline: 2px solid var(--primary); outline-offset: 2px;}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 2rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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: 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="auto-select"> <div #origin class="retro-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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.6rem; font-family: 'Press Start 2P'; --retro-button-color: #fff; --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-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}.retro-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: #000; z-index: 1;}[ngComboboxInput] { width: 15rem; font-size: 0.6rem; border-radius: 0; font-family: 'Press Start 2P'; word-spacing: -5px; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: #000; border: none; box-shadow: var(--retro-flat-shadow); background-color: var(--retro-button-color);}[ngComboboxInput]::placeholder { color: #000; opacity: 0.7;}[ngComboboxInput]:focus { outline: none; transform: translate(1px, 1px); box-shadow: var(--retro-pressed-shadow);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 20px; padding: 0.5rem; max-height: 11rem; border-radius: 0; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}.no-results { padding: 1rem;}[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;}[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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
手動選択モード
手動選択モードでは、ユーザーが候補リストをナビゲートしている間、入力されたテキストは変更されず、自動更新による混乱を防ぎます。入力は、ユーザーがEnterキーまたはクリックで明示的に選択を確定した場合にのみ変更されます。
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox focusMode="manual"> <div #origin class="autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}.autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 0.25rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--hot-pink); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 0.5rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox focusMode="manual"> <div #origin class="material-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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);}.material-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 3rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--primary); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:focus-within [ngComboboxInput] { outline: 2px solid var(--primary); outline-offset: 2px;}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 2rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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: 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox focusMode="manual"> <div #origin class="retro-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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.6rem; font-family: 'Press Start 2P'; --retro-button-color: #fff; --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-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}.retro-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: #000; z-index: 1;}[ngComboboxInput] { width: 15rem; font-size: 0.6rem; border-radius: 0; font-family: 'Press Start 2P'; word-spacing: -5px; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: #000; border: none; box-shadow: var(--retro-flat-shadow); background-color: var(--retro-button-color);}[ngComboboxInput]::placeholder { color: #000; opacity: 0.7;}[ngComboboxInput]:focus { outline: none; transform: translate(1px, 1px); box-shadow: var(--retro-pressed-shadow);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 20px; padding: 0.5rem; max-height: 11rem; border-radius: 0; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}.no-results { padding: 1rem;}[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;}[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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
ハイライトモード
ハイライトモードでは、ユーザーは矢印キーでオプションをナビゲートできますが、Enterキーまたはクリックで明示的に新しいオプションを選択するまで、閲覧中に入力値は変更されません。
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="highlight"> <div #origin class="autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}.autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 0.25rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--hot-pink); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 0.5rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="highlight"> <div #origin class="material-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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);}.material-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: var(--quaternary-contrast);}[ngComboboxInput] { width: 13rem; font-size: 1rem; border-radius: 3rem; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: var(--primary-contrast); outline-color: var(--primary); border: 1px solid var(--quinary-contrast); background-color: var(--page-background);}[ngComboboxInput]::placeholder { color: var(--quaternary-contrast);}[ngCombobox]:focus-within [ngComboboxInput] { outline: 2px solid var(--primary); outline-offset: 2px;}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 8px; padding: 0.5rem; max-height: 11rem; border-radius: 2rem; background-color: var(--septenary-contrast); font-size: 0.9rem;}.no-results { padding: 1rem;}[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: 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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
app.component.ts
import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';import {Listbox, Option} from '@angular/aria/listbox';import {OverlayModule} from '@angular/cdk/overlay';import { afterRenderEffect, ChangeDetectionStrategy, Component, computed, signal, viewChild, viewChildren,} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [ Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class App { /** The combobox listbox popup. */ listbox = viewChild<Listbox<string>>(Listbox); /** The options available in the listbox. */ options = viewChildren<Option<string>>(Option); /** A reference to the ng aria combobox. */ combobox = viewChild<Combobox<string>>(Combobox); /** The query string used to filter the list of countries. */ query = signal(''); /** The list of countries filtered by the query. */ countries = computed(() => ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), ); constructor() { // Scrolls to the active item when the active option changes. // The slight delay here is to ensure animations are done before scrolling. afterRenderEffect(() => { const option = this.options().find((opt) => opt.active()); setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { if (!this.combobox()?.expanded()) { setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); } }); }}const ALL_COUNTRIES = [ 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo (Congo-Brazzaville)', 'Costa Rica', "Côte d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czechia (Czech Republic)', 'Democratic Republic of the Congo', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini (fmr. ""Swaziland"")', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Holy See', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar (formerly Burma)', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine State', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe',];
app.component.html
<div ngCombobox filterMode="highlight"> <div #origin class="retro-autocomplete"> <span class="search-icon material-symbols-outlined" translate="no">search</span> <input aria-label="Label dropdown" placeholder="Select a country" [(ngModel)]="query" ngComboboxInput /> </div> <ng-template ngComboboxPopupContainer> <ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}" [cdkConnectedOverlayOpen]="true" > <div class="popup"> @if (countries().length === 0) { <div class="no-results">No results found</div> } <div ngListbox> @for (country of countries(); track country) { <div ngOption [value]="country" [label]="country"> <span class="option-label">{{country}}</span> <span class="check-icon material-symbols-outlined" translate="no">check</span> </div> } </div> </div> </ng-template> </ng-template></div>
app.component.css
@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.6rem; font-family: 'Press Start 2P'; --retro-button-color: #fff; --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-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}.retro-autocomplete { display: flex; position: relative; align-items: center;}.material-symbols-outlined { font-size: 1.25rem; pointer-events: none;}.search-icon { left: 0.75rem; position: absolute; color: #000; z-index: 1;}[ngComboboxInput] { width: 15rem; font-size: 0.6rem; border-radius: 0; font-family: 'Press Start 2P'; word-spacing: -5px; padding: 0.75rem 0.5rem 0.75rem 2.5rem; color: #000; border: none; box-shadow: var(--retro-flat-shadow); background-color: var(--retro-button-color);}[ngComboboxInput]::placeholder { color: #000; opacity: 0.7;}[ngComboboxInput]:focus { outline: none; transform: translate(1px, 1px); box-shadow: var(--retro-pressed-shadow);}[ngCombobox]:has([aria-expanded='false']) .popup { display: none;}.popup { width: 100%; margin-top: 20px; padding: 0.5rem; max-height: 11rem; border-radius: 0; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}.no-results { padding: 1rem;}[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;}[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']) .check-icon { display: none;}.option-label { flex: 1;}.check-icon { font-size: 0.9rem;}
API
Comboboxディレクティブ
ngComboboxディレクティブは、オートコンプリート機能のためのコンテナを提供します。
入力
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
filterMode |
'auto-select' | 'manual' | 'highlight' |
'manual' |
選択の動作を制御します |
disabled |
boolean |
false |
コンボボックスを無効にします |
firstMatch |
string |
- | ポップアップ内で最初に一致したアイテムの値を表します |
出力
| プロパティ | 型 | 説明 |
|---|---|---|
expanded |
Signal<boolean> |
ポップアップが現在開いているかどうかを示すSignalです |
ComboboxInputディレクティブ
ngComboboxInputディレクティブは、input要素をコンボボックスに接続します。
モデル
| プロパティ | 型 | 説明 |
|---|---|---|
value |
string |
[(value)]を使用した、inputの双方向バインディング可能な文字列値です |
ComboboxPopupContainerディレクティブ
ngComboboxPopupContainerディレクティブは、ポップアップのコンテンツをラップし、その表示を管理します。
popover要素内の<ng-template>と共に使用する必要があります。
関連コンポーネント
オートコンプリートは、候補リストをレンダリングするためにListboxとOptionディレクティブを使用します。追加のカスタマイズオプションについては、Listboxのドキュメントを参照してください。