From f84653605a65e9abd8f5db2fd2c41797fe89e62a Mon Sep 17 00:00:00 2001 From: tjshiu <35056071+tjshiu@users.noreply.github.com> Date: Wed, 20 May 2026 12:36:37 -0700 Subject: [PATCH] docs: modernize autocomplete examples and guide to signal apis --- .../autocomplete/src/assets/autocomplete.css | 89 +++++++ .../aria/autocomplete/src/basic/app/app.css | 113 ++++++++ .../aria/autocomplete/src/basic/app/app.html | 55 ++++ .../aria/autocomplete/src/basic/app/app.ts | 249 ++++++++++++++++++ .../src/basic/material/app/app.css | 114 ++++++++ .../src/basic/material/app/app.html | 55 ++++ .../src/basic/material/app/app.ts | 249 ++++++++++++++++++ .../autocomplete/src/basic/retro/app/app.css | 135 ++++++++++ .../autocomplete/src/basic/retro/app/app.html | 55 ++++ .../autocomplete/src/basic/retro/app/app.ts | 249 ++++++++++++++++++ .../autocomplete/src/highlight/app/app.css | 113 ++++++++ .../autocomplete/src/highlight/app/app.html | 57 ++++ .../autocomplete/src/highlight/app/app.ts | 247 +++++++++++++++++ .../src/highlight/material/app/app.css | 114 ++++++++ .../src/highlight/material/app/app.html | 57 ++++ .../src/highlight/material/app/app.ts | 247 +++++++++++++++++ .../src/highlight/retro/app/app.css | 135 ++++++++++ .../src/highlight/retro/app/app.html | 57 ++++ .../src/highlight/retro/app/app.ts | 248 +++++++++++++++++ .../aria/autocomplete/src/manual/app/app.css | 113 ++++++++ .../aria/autocomplete/src/manual/app/app.html | 55 ++++ .../aria/autocomplete/src/manual/app/app.ts | 238 +++++++++++++++++ .../src/manual/material/app/app.css | 114 ++++++++ .../src/manual/material/app/app.html | 55 ++++ .../src/manual/material/app/app.ts | 238 +++++++++++++++++ .../autocomplete/src/manual/retro/app/app.css | 135 ++++++++++ .../src/manual/retro/app/app.html | 55 ++++ .../autocomplete/src/manual/retro/app/app.ts | 238 +++++++++++++++++ adev/src/content/guide/aria/autocomplete.md | 89 +++++-- 29 files changed, 3939 insertions(+), 29 deletions(-) create mode 100644 adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html create mode 100644 adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts diff --git a/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css b/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css new file mode 100644 index 00000000000..45e65487e77 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css @@ -0,0 +1,89 @@ +html { + font-family: var(--inter-font); +} + +.combobox-container { + max-width: 400px; + margin: 20px; +} + +label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #e0e0e0; +} + +.input-container { + position: relative; +} + +.combobox-input { + width: 100%; + padding: 10px 12px; + border: 1px solid #404040; + border-radius: 4px; + font-size: 16px; + box-sizing: border-box; + background-color: #1a1a1a; + color: #e0e0e0; +} + +.combobox-input::placeholder { + color: #888; +} + +.combobox-input:focus { + outline: none; + border-color: #4a9eff; + background-color: #1f1f1f; +} + +.popover { + margin: 0; + padding: 0; + border: 1px solid #404040; + border-radius: 4px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); + background: #1a1a1a; + max-height: 300px; + overflow-y: auto; +} + +.listbox { + padding: 4px 0; +} + +.option { + padding: 10px 12px; + cursor: pointer; + user-select: none; + color: #e0e0e0; +} + +.option:hover { + background-color: #2a2a2a; +} + +.option[data-active] { + background-color: #2d4a6e; + color: #ffffff; +} + +.option[aria-selected='true'] { + background-color: #4a9eff; + color: #000000; +} + +.info { + margin: 20px; + padding: 16px; + background-color: #1f1f1f; + border-radius: 4px; + border-left: 4px solid #4a9eff; + color: #e0e0e0; +} + +.info p { + margin: 8px 0; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css new file mode 100644 index 00000000000..5ba77d21265 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css @@ -0,0 +1,113 @@ +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-family: var(--inter-font); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.autocomplete-input-container { + 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); +} + +.autocomplete-input { + width: 13rem; + font-size: 1rem; + border-radius: 0.25rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; + color: var(--primary-contrast); + outline: none; + border: 1px solid var(--quinary-contrast); + background-color: var(--page-background); +} + +.autocomplete-input:focus-visible { + border-color: var(--hot-pink); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent); +} + +.autocomplete-input::placeholder { + color: var(--quaternary-contrast); +} + +.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; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.no-results { + padding: 1rem; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 2.25rem; + border-radius: 0.5rem; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--hot-pink); +} + +.option[aria-selected='true'] { + color: var(--hot-pink); + background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html new file mode 100644 index 00000000000..70f87e13574 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts new file mode 100644 index 00000000000..f94c55b1c01 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts @@ -0,0 +1,249 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="basic-basic"], app-root:not([theme])', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + } + + onBlur() { + this.commitSelection(); + } + + onCommit() { + this.commitSelection(); + this.popupExpanded.set(false); + } + + private commitSelection() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } else { + this.query.set(''); + this.selectedOption.set([]); + } + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css new file mode 100644 index 00000000000..00237755e97 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css @@ -0,0 +1,114 @@ +@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); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.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); +} + +.autocomplete-input { + width: 13rem; + font-size: 1rem; + border-radius: 3rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; + color: var(--primary-contrast); + outline: none; + border: 1px solid var(--quinary-contrast); + background-color: var(--page-background); +} + +.autocomplete-input:focus-visible { + border-color: var(--primary); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent); +} + +.autocomplete-input::placeholder { + color: var(--quaternary-contrast); +} + +.popup { + width: 100%; + margin-top: 8px; + padding: 0.5rem; + max-height: 11rem; + border-radius: 2rem; + background-color: var(--septenary-contrast); + font-size: 0.9rem; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.no-results { + padding: 1rem; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 3rem; + border-radius: 3rem; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--primary); +} + +.option[aria-selected='true'] { + color: var(--primary); + background-color: color-mix(in srgb, var(--primary) 10%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html new file mode 100644 index 00000000000..e2bce42676a --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts new file mode 100644 index 00000000000..fc5c29a32a6 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts @@ -0,0 +1,249 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="basic-material"]', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + } + + onBlur() { + this.commitSelection(); + } + + onCommit() { + this.commitSelection(); + this.popupExpanded.set(false); + } + + private commitSelection() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } else { + this.query.set(''); + this.selectedOption.set([]); + } + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css new file mode 100644 index 00000000000..42909193677 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css @@ -0,0 +1,135 @@ +@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(--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: + 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-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(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.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; +} + +.autocomplete-input { + 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); + outline: none; +} + +.autocomplete-input:focus-visible { + outline: none; + transform: translate(1px, 1px); + box-shadow: var(--retro-pressed-shadow); +} + +.autocomplete-input::placeholder { + color: #000; + opacity: 0.7; +} + +.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; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 2.25rem; + border-radius: 0; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px dashed var(--hot-pink); +} + +.option[aria-selected='true'] { + color: var(--hot-pink); + background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html new file mode 100644 index 00000000000..09305a17d81 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts new file mode 100644 index 00000000000..928ef15cebc --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts @@ -0,0 +1,249 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="basic-retro"]', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + } + + onBlur() { + this.commitSelection(); + } + + onCommit() { + this.commitSelection(); + this.popupExpanded.set(false); + } + + private commitSelection() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } else { + this.query.set(''); + this.selectedOption.set([]); + } + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css new file mode 100644 index 00000000000..5ba77d21265 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css @@ -0,0 +1,113 @@ +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-family: var(--inter-font); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.autocomplete-input-container { + 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); +} + +.autocomplete-input { + width: 13rem; + font-size: 1rem; + border-radius: 0.25rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; + color: var(--primary-contrast); + outline: none; + border: 1px solid var(--quinary-contrast); + background-color: var(--page-background); +} + +.autocomplete-input:focus-visible { + border-color: var(--hot-pink); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent); +} + +.autocomplete-input::placeholder { + color: var(--quaternary-contrast); +} + +.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; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.no-results { + padding: 1rem; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 2.25rem; + border-radius: 0.5rem; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--hot-pink); +} + +.option[aria-selected='true'] { + color: var(--hot-pink); + background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html new file mode 100644 index 00000000000..d7d68d56f7e --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html @@ -0,0 +1,57 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts new file mode 100644 index 00000000000..5c9eb0dcf3a --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts @@ -0,0 +1,247 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="highlight-basic"], app-root:not([theme])', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + navigated = signal(false); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + + effect(() => { + if (!this.popupExpanded()) { + this.navigated.set(false); + } + }); + } + + onCommit() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } else { + this.query.set(''); + } + this.popupExpanded.set(false); + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css new file mode 100644 index 00000000000..00237755e97 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css @@ -0,0 +1,114 @@ +@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); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.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); +} + +.autocomplete-input { + width: 13rem; + font-size: 1rem; + border-radius: 3rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; + color: var(--primary-contrast); + outline: none; + border: 1px solid var(--quinary-contrast); + background-color: var(--page-background); +} + +.autocomplete-input:focus-visible { + border-color: var(--primary); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent); +} + +.autocomplete-input::placeholder { + color: var(--quaternary-contrast); +} + +.popup { + width: 100%; + margin-top: 8px; + padding: 0.5rem; + max-height: 11rem; + border-radius: 2rem; + background-color: var(--septenary-contrast); + font-size: 0.9rem; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.no-results { + padding: 1rem; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 3rem; + border-radius: 3rem; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--primary); +} + +.option[aria-selected='true'] { + color: var(--primary); + background-color: color-mix(in srgb, var(--primary) 10%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html new file mode 100644 index 00000000000..f307c227b01 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html @@ -0,0 +1,57 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts new file mode 100644 index 00000000000..20d93170f8e --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts @@ -0,0 +1,247 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="highlight-material"]', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + navigated = signal(false); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + + effect(() => { + if (!this.popupExpanded()) { + this.navigated.set(false); + } + }); + } + + onCommit() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } else { + this.query.set(''); + } + this.popupExpanded.set(false); + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css new file mode 100644 index 00000000000..42909193677 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css @@ -0,0 +1,135 @@ +@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(--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: + 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-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(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.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; +} + +.autocomplete-input { + 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); + outline: none; +} + +.autocomplete-input:focus-visible { + outline: none; + transform: translate(1px, 1px); + box-shadow: var(--retro-pressed-shadow); +} + +.autocomplete-input::placeholder { + color: #000; + opacity: 0.7; +} + +.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; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 2.25rem; + border-radius: 0; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px dashed var(--hot-pink); +} + +.option[aria-selected='true'] { + color: var(--hot-pink); + background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html new file mode 100644 index 00000000000..2ff04f86414 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html @@ -0,0 +1,57 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts new file mode 100644 index 00000000000..4915fc80e11 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts @@ -0,0 +1,248 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="highlight-retro"]', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + navigated = signal(false); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + + effect(() => { + if (!this.popupExpanded()) { + this.navigated.set(false); + } + }); + } + + onCommit() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } else { + this.query.set(''); + } + this.popupExpanded.set(false); + } +} + +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', + 'Imporant', + '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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.css b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.css new file mode 100644 index 00000000000..5ba77d21265 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.css @@ -0,0 +1,113 @@ +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-family: var(--inter-font); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.autocomplete-input-container { + 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); +} + +.autocomplete-input { + width: 13rem; + font-size: 1rem; + border-radius: 0.25rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; + color: var(--primary-contrast); + outline: none; + border: 1px solid var(--quinary-contrast); + background-color: var(--page-background); +} + +.autocomplete-input:focus-visible { + border-color: var(--hot-pink); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent); +} + +.autocomplete-input::placeholder { + color: var(--quaternary-contrast); +} + +.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; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.no-results { + padding: 1rem; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 2.25rem; + border-radius: 0.5rem; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--hot-pink); +} + +.option[aria-selected='true'] { + color: var(--hot-pink); + background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.html b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.html new file mode 100644 index 00000000000..8bbf29ff983 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts new file mode 100644 index 00000000000..cc4e7f72f2c --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts @@ -0,0 +1,238 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="manual-basic"], app-root:not([theme])', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + } + + onCommit() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } + this.popupExpanded.set(false); + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css new file mode 100644 index 00000000000..00237755e97 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css @@ -0,0 +1,114 @@ +@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); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.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); +} + +.autocomplete-input { + width: 13rem; + font-size: 1rem; + border-radius: 3rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; + color: var(--primary-contrast); + outline: none; + border: 1px solid var(--quinary-contrast); + background-color: var(--page-background); +} + +.autocomplete-input:focus-visible { + border-color: var(--primary); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent); +} + +.autocomplete-input::placeholder { + color: var(--quaternary-contrast); +} + +.popup { + width: 100%; + margin-top: 8px; + padding: 0.5rem; + max-height: 11rem; + border-radius: 2rem; + background-color: var(--septenary-contrast); + font-size: 0.9rem; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.no-results { + padding: 1rem; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 3rem; + border-radius: 3rem; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--primary); +} + +.option[aria-selected='true'] { + color: var(--primary); + background-color: color-mix(in srgb, var(--primary) 10%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html new file mode 100644 index 00000000000..bebc39187fc --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts new file mode 100644 index 00000000000..79d043ae3a8 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts @@ -0,0 +1,238 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="manual-material"]', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + } + + onCommit() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } + this.popupExpanded.set(false); + } +} + +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', +]; diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css new file mode 100644 index 00000000000..42909193677 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css @@ -0,0 +1,135 @@ +@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(--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: + 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-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(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); +} + +.autocomplete-container { + display: flex; + flex-direction: column; + position: relative; +} + +.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; +} + +.autocomplete-input { + 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); + outline: none; +} + +.autocomplete-input:focus-visible { + outline: none; + transform: translate(1px, 1px); + box-shadow: var(--retro-pressed-shadow); +} + +.autocomplete-input::placeholder { + color: #000; + opacity: 0.7; +} + +.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; +} + +.listbox { + gap: 2px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; + outline: none; +} + +.option { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + padding: 0 1rem; + min-height: 2.25rem; + border-radius: 0; + outline: none; +} + +.option:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +.option[data-active='true'] { + outline-offset: -2px; + outline: 2px dashed var(--hot-pink); +} + +.option[aria-selected='true'] { + color: var(--hot-pink); + background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); +} + +.option:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.check-icon { + font-size: 0.9rem; +} diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html new file mode 100644 index 00000000000..2db970ae645 --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ {{ countries().length === 0 ? 'No results found for ' + query() : '' }} +
+ + + + + + +
diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts new file mode 100644 index 00000000000..d766b47203b --- /dev/null +++ b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts @@ -0,0 +1,238 @@ +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-root[theme="manual-retro"]', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], +}) +export class App { + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); + + popupExpanded = signal(false); + query = signal(''); + selectedOption = signal([]); + + countries = computed(() => + ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded() === true) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + } + + onCommit() { + const selected = this.selectedOption(); + if (selected.length > 0) { + this.query.set(selected[0]); + } + this.popupExpanded.set(false); + } +} + +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', +]; diff --git a/adev/src/content/guide/aria/autocomplete.md b/adev/src/content/guide/aria/autocomplete.md index 8646256a97d..e7f629a124f 100644 --- a/adev/src/content/guide/aria/autocomplete.md +++ b/adev/src/content/guide/aria/autocomplete.md @@ -5,7 +5,7 @@ An accessible input field that filters and suggests options as users type, helping them find and select values from a list. - + ## Usage @@ -52,7 +52,7 @@ Angular's autocomplete provides a fully accessible combobox implementation with: - **Keyboard Navigation** - Navigate options with arrow keys, select with Enter, close with Escape - **Screen Reader Support** - Built-in ARIA attributes for assistive technologies -- **Three Filter Modes** - Choose between auto-select, manual selection, or highlighting behavior +- **Dynamic Highlight Behavior** - Built-in support for inline selection suggestions - **Signal-Based Reactivity** - Reactive state management using Angular signals - **Popover API Integration** - Leverages the native HTML Popover API for optimal positioning - **Bidirectional Text Support** - Automatically handles right-to-left (RTL) languages @@ -63,7 +63,7 @@ Angular's autocomplete provides a fully accessible combobox implementation with: Users typing partial text expect immediate confirmation that their input matches an available option. Auto-select mode updates the input value to match the first filtered option as users type, reducing the number of keystrokes needed and providing instant feedback that their search is on the right track. - + ### Manual selection mode Manual selection mode keeps the typed text unchanged while users navigate the suggestion list, preventing confusion from automatic updates. The input only changes when users explicitly confirm their choice with Enter or a click. - + ### Highlight mode Highlight mode allows the user to navigate options with arrow keys without changing the input value as they browse until they explicitly select a new option with Enter or click. - + ## APIs ### Combobox Directive -The `ngCombobox` directive provides the container for autocomplete functionality. +The `ngCombobox` directive is applied directly onto the editable text `` or `