+
+
+
+ search
+
+
-
- @if (filteredOptions().length === 0) {
- No results found
- }
+
+ {{ filteredOptions().length === 0 ? 'No results found for ' + searchString() : '' }}
+
-
- @for (option of filteredOptions(); track option.value) {
-
- {{ option.label }}
- check
+
- }
+
-
+
-
+
diff --git a/adev/shared-docs/components/select/select.component.scss b/adev/shared-docs/components/select/select.component.scss
index cc9d81910c4f..90a88ae091f5 100644
--- a/adev/shared-docs/components/select/select.component.scss
+++ b/adev/shared-docs/components/select/select.component.scss
@@ -1,8 +1,6 @@
:host {
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
-}
-[ngCombobox] {
position: relative;
width: 100%;
display: flex;
@@ -16,15 +14,25 @@
position: relative;
align-items: center;
border-radius: 0.25rem;
-}
-
-[ngComboboxInput] {
- border-radius: 0.25rem;
-}
-[ngComboboxInput][readonly='true'] {
- cursor: pointer;
- padding: 0.7rem 1rem;
+ input {
+ width: 100%;
+ border: none;
+ outline: none;
+ font-size: 1rem;
+ padding: 0.7rem 1rem 0.7rem;
+ background-color: var(--septenary-contrast);
+ color: var(--primary-contrast);
+
+ &[readonly] {
+ cursor: pointer;
+ padding: 0.7rem 1rem;
+ }
+
+ &[aria-expanded='true'] + .docs-select-arrow {
+ transform: rotate(180deg);
+ }
+ }
}
[ngCombobox]:focus-within [ngComboboxInput]:not(.docs-select-search-input) {
@@ -32,6 +40,10 @@
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent);
}
+.docs-select-popover {
+ width: 100%;
+}
+
.docs-select-arrow {
width: 24px;
height: 24px;
@@ -46,16 +58,11 @@
transition: transform 0.2s ease;
}
-[ngComboboxInput][aria-expanded='true'] + .docs-select-arrow {
- transform: rotate(180deg);
-}
-
-[ngComboboxInput] {
- width: 100%;
+[ngComboboxInput] [ngCombobox] {
border: none;
outline: none;
font-size: 1rem;
- padding: 0.7rem 1rem 0.7rem 2.5rem;
+ padding: 0.7rem 1rem 0.7rem;
background-color: var(--septenary-contrast);
color: var(--primary-contrast);
}
diff --git a/adev/shared-docs/components/select/select.component.ts b/adev/shared-docs/components/select/select.component.ts
index 1ff0554a8d28..99b0cf00a6a9 100644
--- a/adev/shared-docs/components/select/select.component.ts
+++ b/adev/shared-docs/components/select/select.component.ts
@@ -1,4 +1,4 @@
-/*!
+/**
* @license
* Copyright Google LLC All Rights Reserved.
*
@@ -6,13 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/
-import {
- Combobox,
- ComboboxDialog,
- ComboboxInput,
- ComboboxPopupContainer,
-} from '@angular/aria/combobox';
+import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
+import {OverlayModule} from '@angular/cdk/overlay';
import {
afterRenderEffect,
Component,
@@ -20,13 +16,11 @@ import {
input,
model,
signal,
- untracked,
viewChild,
} from '@angular/core';
-import {FormsModule} from '@angular/forms';
import {FormValueControl} from '@angular/forms/signals';
-type SelectOptionValue = string | number | boolean;
+type SelectOptionValue = string;
export interface SelectOption {
label: string;
@@ -35,17 +29,9 @@ export interface SelectOption {
@Component({
selector: 'docs-select',
- imports: [
- Combobox,
- ComboboxDialog,
- ComboboxInput,
- ComboboxPopupContainer,
- FormsModule,
- Listbox,
- Option,
- ],
- templateUrl: './select.component.html',
- styleUrls: ['./select.component.scss'],
+ templateUrl: 'select.component.html',
+ styleUrl: 'select.component.css',
+ imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class Select implements FormValueControl
{
readonly value = model(null);
@@ -55,12 +41,12 @@ export class Select implements FormValueControl {
readonly options = input.required();
readonly disabled = input(false);
- readonly dialog = viewChild(ComboboxDialog);
- readonly listbox = viewChild>(Listbox);
- readonly combobox = viewChild>(Combobox);
+ readonly listbox = viewChild(Listbox);
+ readonly combobox = viewChild(Combobox);
readonly searchString = signal('');
+ readonly popupExpanded = signal(false);
readonly filteredOptions = computed(() => {
const search = this.searchString().toLowerCase();
if (!search) {
@@ -82,39 +68,36 @@ export class Select implements FormValueControl {
constructor() {
afterRenderEffect(() => {
- if (this.dialog() && this.combobox()?.expanded()) {
- untracked(() => this.listbox()?.gotoFirst());
- this.positionDialog();
- }
+ this.listbox()?.scrollActiveItemIntoView();
});
-
- afterRenderEffect(() => {
- const selected = this.selectedValues();
- if (selected.length > 0) {
- untracked(() => this.dialog()?.close());
- this.value.set(selected[0] as string);
- this.searchString.set('');
- }
- });
-
- afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView());
}
- // TODO: Improve once CDK overlay is fixed https://github.com/angular/components/issues/32504
- private positionDialog(): void {
- const dialog = this.dialog();
- const combobox = this.combobox();
- if (!dialog || !combobox) {
- return;
+ onCommit() {
+ const values = this.selectedValues();
+ if (values.length) {
+ this.value.set(values[0]);
+ //this.popupExpanded.set(false);
}
+ }
- const comboboxRect = combobox.inputElement()?.getBoundingClientRect();
- const scrollY = window.scrollY;
+ /** Dismisses the dialog overlay on Escape key. */
+ onSearchEscape(event: Event) {
+ this.popupExpanded.set(false);
+ this.combobox()?.element.focus();
+ }
- if (comboboxRect) {
- dialog.element.style.width = `${comboboxRect.width}px`;
- dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`;
- dialog.element.style.left = `${comboboxRect.left}px`;
+ /** Handles keydown events on the clear button. */
+ onKeydown(event: KeyboardEvent): void {
+ if (event.key === 'Enter') {
+ this.clear();
+ this.popupExpanded.set(false);
+ event.stopPropagation();
}
}
+
+ /** Clears the search query and all selected options. */
+ clear(): void {
+ this.searchString.set('');
+ this.value.set(null);
+ }
}
diff --git a/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css b/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css
deleted file mode 100644
index 45e65487e775..000000000000
--- a/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css
+++ /dev/null
@@ -1,89 +0,0 @@
-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
deleted file mode 100644
index c7aee9916952..000000000000
--- a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css
+++ /dev/null
@@ -1,101 +0,0 @@
-@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;
-}
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
deleted file mode 100644
index 583ced285ed4..000000000000
--- a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
- search
-
-
-
-
-
-
-
-
-
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
deleted file mode 100644
index 135575be95bf..000000000000
--- a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts
+++ /dev/null
@@ -1,259 +0,0 @@
-import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox';
-import {Listbox, Option} from '@angular/aria/listbox';
-import {OverlayModule} from '@angular/cdk/overlay';
-import {
- afterRenderEffect,
- Component,
- computed,
- signal,
- viewChild,
- viewChildren,
-} from '@angular/core';
-import {FormsModule} from '@angular/forms';
-
-@Component({
- selector: 'app-root',
- templateUrl: 'app.html',
- styleUrl: 'app.css',
- imports: [
- Combobox,
- ComboboxInput,
- ComboboxPopupContainer,
- Listbox,
- Option,
- OverlayModule,
- FormsModule,
- ],
-})
-export class App {
- /** The combobox listbox popup. */
- listbox = viewChild>(Listbox);
-
- /** The options available in the listbox. */
- options = viewChildren