From 372d20be9da2c755862b544c9667f6e2ceb0c56f Mon Sep 17 00:00:00 2001 From: Ruslan Lekhman Date: Wed, 29 Oct 2025 21:43:59 -0600 Subject: [PATCH 1/2] fix(material/chips): add only active autocomplete option on selection close #32204, #13574 --- goldens/material/chips/index.api.md | 14 +++++++++++-- src/material/chips/BUILD.bazel | 1 + src/material/chips/chip-input.ts | 31 +++++++++++++++++++++++++++-- src/material/chips/chips-module.ts | 3 ++- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index 0cc3258191bf..80a363d6b49f 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -132,6 +132,15 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public (undocumented) +export class MatChipAutocompleteInput extends MatChipInput { + _emitChipEnd(event?: KeyboardEvent): void; + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public export class MatChipAvatar { // (undocumented) @@ -288,6 +297,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { protected _getReadonlyAttribute(): string | null; id: string; readonly inputElement: HTMLInputElement; + protected _isSeparatorKey(event: KeyboardEvent): boolean; _keydown(event: KeyboardEvent): void; // (undocumented) static ngAcceptInputType_addOnBlur: unknown; @@ -309,7 +319,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { // (undocumented) setDescribedByIds(ids: string[]): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -541,7 +551,7 @@ export class MatChipsModule { // (undocumented) static ɵinj: i0.ɵɵInjectorDeclaration; // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public diff --git a/src/material/chips/BUILD.bazel b/src/material/chips/BUILD.bazel index 0146ffcfaa3e..f2e97b440dfd 100644 --- a/src/material/chips/BUILD.bazel +++ b/src/material/chips/BUILD.bazel @@ -100,6 +100,7 @@ ng_project( "//:node_modules/@angular/forms", "//:node_modules/rxjs", "//src:dev_mode_types", + "//src/material/autocomplete", "//src/material/core", "//src/material/form-field", ], diff --git a/src/material/chips/chip-input.ts b/src/material/chips/chip-input.ts index b7305e7fb88f..8bc695f00715 100644 --- a/src/material/chips/chip-input.ts +++ b/src/material/chips/chip-input.ts @@ -19,6 +19,7 @@ import { inject, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; +import {MatAutocompleteTrigger} from '../autocomplete'; import {MatFormField, MAT_FORM_FIELD} from '../form-field'; import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS, SeparatorKey} from './tokens'; import {MatChipGrid} from './chip-grid'; @@ -45,7 +46,7 @@ export interface MatChipInputEvent { * May be placed inside or outside of a ``. */ @Directive({ - selector: 'input[matChipInputFor]', + selector: 'input[matChipInputFor]:not([matAutocomplete])', exportAs: 'matChipInput, matChipInputFor', host: { // TODO: eventually we should remove `mat-input-element` from here since it comes from the @@ -242,7 +243,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { } /** Checks whether a keycode is one of the configured separators. */ - private _isSeparatorKey(event: KeyboardEvent): boolean { + protected _isSeparatorKey(event: KeyboardEvent): boolean { if (!this.separatorKeyCodes) { return false; } @@ -276,3 +277,29 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { return this.readonly || (this.disabled && this.disabledInteractive) ? 'true' : null; } } + +/** + * Directive that adds chip-specific behaviors to an autocomplete input element inside + * ``. + * May be placed inside or outside of a ``. + */ +@Directive({ + selector: 'input[matChipInputFor][matAutocomplete]', + exportAs: 'matChipInput, matChipInputFor', +}) +export class MatChipAutocompleteInput extends MatChipInput { + private readonly _autocompleteTrigger = inject(MatAutocompleteTrigger); + + /** Checks to see if the (chipEnd) event needs to be emitted. */ + override _emitChipEnd(event?: KeyboardEvent) { + if ( + (!event || (super._isSeparatorKey(event) && !event.repeat)) && + this._autocompleteTrigger.autocomplete.isOpen && + this._autocompleteTrigger.activeOption + ) { + event?.preventDefault(); + } else { + super._emitChipEnd(event); + } + } +} diff --git a/src/material/chips/chips-module.ts b/src/material/chips/chips-module.ts index 7970d1fe1bf8..5389e59db9d0 100644 --- a/src/material/chips/chips-module.ts +++ b/src/material/chips/chips-module.ts @@ -14,7 +14,7 @@ import {MAT_CHIPS_DEFAULT_OPTIONS, MatChipsDefaultOptions} from './tokens'; import {MatChipEditInput} from './chip-edit-input'; import {MatChipGrid} from './chip-grid'; import {MatChipAvatar, MatChipEdit, MatChipRemove, MatChipTrailingIcon} from './chip-icons'; -import {MatChipInput} from './chip-input'; +import {MatChipAutocompleteInput, MatChipInput} from './chip-input'; import {MatChipListbox} from './chip-listbox'; import {MatChipRow} from './chip-row'; import {MatChipOption} from './chip-option'; @@ -28,6 +28,7 @@ const CHIP_DECLARATIONS = [ MatChipEdit, MatChipEditInput, MatChipGrid, + MatChipAutocompleteInput, MatChipInput, MatChipListbox, MatChipOption, From e7e71ef7bb8fd2671a91f67389199419e42b1944 Mon Sep 17 00:00:00 2001 From: Ruslan Lekhman Date: Thu, 30 Oct 2025 09:43:04 -0600 Subject: [PATCH 2/2] docs(material/chips): add only active autocomplete option on selection close #32204, #13574 --- goldens/material/chips/index.api.md | 14 ++------- .../chips-autocomplete-example.ts | 11 +++++-- src/material/chips/BUILD.bazel | 1 - src/material/chips/chip-input.ts | 31 ++----------------- src/material/chips/chips-module.ts | 3 +- src/material/chips/chips.md | 5 ++- 6 files changed, 17 insertions(+), 48 deletions(-) diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index 80a363d6b49f..0cc3258191bf 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -132,15 +132,6 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public (undocumented) -export class MatChipAutocompleteInput extends MatChipInput { - _emitChipEnd(event?: KeyboardEvent): void; - // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; - // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; -} - // @public export class MatChipAvatar { // (undocumented) @@ -297,7 +288,6 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { protected _getReadonlyAttribute(): string | null; id: string; readonly inputElement: HTMLInputElement; - protected _isSeparatorKey(event: KeyboardEvent): boolean; _keydown(event: KeyboardEvent): void; // (undocumented) static ngAcceptInputType_addOnBlur: unknown; @@ -319,7 +309,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { // (undocumented) setDescribedByIds(ids: string[]): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -551,7 +541,7 @@ export class MatChipsModule { // (undocumented) static ɵinj: i0.ɵɵInjectorDeclaration; // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public diff --git a/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts b/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts index c5be527894b1..8ca45828cfc7 100644 --- a/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts +++ b/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts @@ -2,8 +2,11 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; import {COMMA, ENTER} from '@angular/cdk/keycodes'; import {ChangeDetectionStrategy, Component, computed, inject, model, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; -import {MatAutocompleteModule, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'; -import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips'; +import { + MatAutocompleteModule, + type MatAutocompleteSelectedEvent, +} from '@angular/material/autocomplete'; +import {type MatChipInputEvent, MatChipsModule} from '@angular/material/chips'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatIconModule} from '@angular/material/icon'; @@ -14,7 +17,9 @@ import {MatIconModule} from '@angular/material/icon'; selector: 'chips-autocomplete-example', templateUrl: 'chips-autocomplete-example.html', styleUrl: 'chips-autocomplete-example.css', - imports: [MatFormFieldModule, MatChipsModule, MatIconModule, MatAutocompleteModule, FormsModule], + // Make sure to import `MatAutocompleteModule` before `MatChipsModule` to prevent adding typed + // text when autocomplete option is selected via keyboard). + imports: [MatFormFieldModule, MatAutocompleteModule, MatChipsModule, MatIconModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsAutocompleteExample { diff --git a/src/material/chips/BUILD.bazel b/src/material/chips/BUILD.bazel index f2e97b440dfd..0146ffcfaa3e 100644 --- a/src/material/chips/BUILD.bazel +++ b/src/material/chips/BUILD.bazel @@ -100,7 +100,6 @@ ng_project( "//:node_modules/@angular/forms", "//:node_modules/rxjs", "//src:dev_mode_types", - "//src/material/autocomplete", "//src/material/core", "//src/material/form-field", ], diff --git a/src/material/chips/chip-input.ts b/src/material/chips/chip-input.ts index 8bc695f00715..b7305e7fb88f 100644 --- a/src/material/chips/chip-input.ts +++ b/src/material/chips/chip-input.ts @@ -19,7 +19,6 @@ import { inject, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; -import {MatAutocompleteTrigger} from '../autocomplete'; import {MatFormField, MAT_FORM_FIELD} from '../form-field'; import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS, SeparatorKey} from './tokens'; import {MatChipGrid} from './chip-grid'; @@ -46,7 +45,7 @@ export interface MatChipInputEvent { * May be placed inside or outside of a ``. */ @Directive({ - selector: 'input[matChipInputFor]:not([matAutocomplete])', + selector: 'input[matChipInputFor]', exportAs: 'matChipInput, matChipInputFor', host: { // TODO: eventually we should remove `mat-input-element` from here since it comes from the @@ -243,7 +242,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { } /** Checks whether a keycode is one of the configured separators. */ - protected _isSeparatorKey(event: KeyboardEvent): boolean { + private _isSeparatorKey(event: KeyboardEvent): boolean { if (!this.separatorKeyCodes) { return false; } @@ -277,29 +276,3 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { return this.readonly || (this.disabled && this.disabledInteractive) ? 'true' : null; } } - -/** - * Directive that adds chip-specific behaviors to an autocomplete input element inside - * ``. - * May be placed inside or outside of a ``. - */ -@Directive({ - selector: 'input[matChipInputFor][matAutocomplete]', - exportAs: 'matChipInput, matChipInputFor', -}) -export class MatChipAutocompleteInput extends MatChipInput { - private readonly _autocompleteTrigger = inject(MatAutocompleteTrigger); - - /** Checks to see if the (chipEnd) event needs to be emitted. */ - override _emitChipEnd(event?: KeyboardEvent) { - if ( - (!event || (super._isSeparatorKey(event) && !event.repeat)) && - this._autocompleteTrigger.autocomplete.isOpen && - this._autocompleteTrigger.activeOption - ) { - event?.preventDefault(); - } else { - super._emitChipEnd(event); - } - } -} diff --git a/src/material/chips/chips-module.ts b/src/material/chips/chips-module.ts index 5389e59db9d0..7970d1fe1bf8 100644 --- a/src/material/chips/chips-module.ts +++ b/src/material/chips/chips-module.ts @@ -14,7 +14,7 @@ import {MAT_CHIPS_DEFAULT_OPTIONS, MatChipsDefaultOptions} from './tokens'; import {MatChipEditInput} from './chip-edit-input'; import {MatChipGrid} from './chip-grid'; import {MatChipAvatar, MatChipEdit, MatChipRemove, MatChipTrailingIcon} from './chip-icons'; -import {MatChipAutocompleteInput, MatChipInput} from './chip-input'; +import {MatChipInput} from './chip-input'; import {MatChipListbox} from './chip-listbox'; import {MatChipRow} from './chip-row'; import {MatChipOption} from './chip-option'; @@ -28,7 +28,6 @@ const CHIP_DECLARATIONS = [ MatChipEdit, MatChipEditInput, MatChipGrid, - MatChipAutocompleteInput, MatChipInput, MatChipListbox, MatChipOption, diff --git a/src/material/chips/chips.md b/src/material/chips/chips.md index 3464f0acddbd..6e52171d810a 100644 --- a/src/material/chips/chips.md +++ b/src/material/chips/chips.md @@ -65,6 +65,9 @@ Users can press delete to remove a chip. Pressing delete triggers the `removed` A `` can be combined with `` to enable free-form chip input with suggestions. +> _Please note: when using `MatChipsModule` together with `MatAutocompleteModule`, the order in which modules are imported matters._ +> _To ensure correct behavior (e.g., preventing adding typed text when autocomplete option is selected via keyboard), make sure to import `MatAutocompleteModule` before `MatChipsModule`._ + ### Icons @@ -141,7 +144,7 @@ The chips components support 3 user interaction patterns, each with its own cont `` and `` : These elements implement a grid accessibility pattern. Use them as part of a free form input that allows users to enter text to add chips. -Note : be sure to have the input element be a sibling of mat-chip-grid to ensure accessibility of the input element by accessibility devices such as Voice Control. It is also recommended to apply an appropriate `aria-label` to the input to optimize accessibility of the input. +> _Please note: be sure to have the input element be a sibling of `mat-chip-grid` to ensure accessibility of the input element by accessibility devices such as Voice Control. It is also recommended to apply an appropriate `aria-label` to the input to optimize accessibility of the input._ ```html