diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index 9ced63b6784d..0cc3258191bf 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -19,6 +19,7 @@ import * as i0 from '@angular/core'; import * as i2 from '@angular/cdk/bidi'; import { InjectionToken } from '@angular/core'; import { Injector } from '@angular/core'; +import { ModifierKey } from '@angular/cdk/keycodes'; import { NgControl } from '@angular/forms'; import { NgForm } from '@angular/forms'; import { NgZone } from '@angular/core'; @@ -304,7 +305,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { _onInput(): void; placeholder: string; readonly: boolean; - separatorKeyCodes: readonly number[] | ReadonlySet; + separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet; // (undocumented) setDescribedByIds(ids: string[]): void; // (undocumented) @@ -472,7 +473,7 @@ export class MatChipRow extends MatChip implements AfterViewInit { export interface MatChipsDefaultOptions { hideSingleSelectionIndicator?: boolean; inputDisabledInteractive?: boolean; - separatorKeyCodes: readonly number[] | ReadonlySet; + separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet; } // @public @@ -564,6 +565,14 @@ export class MatChipTrailingIcon extends MatChipContent { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public +export interface SeparatorKey { + // (undocumented) + keyCode: number; + // (undocumented) + modifiers: readonly ModifierKey[]; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/src/material/chips/chip-input.spec.ts b/src/material/chips/chip-input.spec.ts index 7bc043c2e321..a4d8abe30568 100644 --- a/src/material/chips/chip-input.spec.ts +++ b/src/material/chips/chip-input.spec.ts @@ -303,6 +303,40 @@ describe('MatChipInput', () => { expect(testChipInput.add).not.toHaveBeenCalled(); }); + + it('should ignore modifier keys when `SeparatorKey.modifiers` is empty', () => { + spyOn(testChipInput, 'add'); + + chipInputDirective.separatorKeyCodes = [{keyCode: COMMA, modifiers: []}]; + fixture.detectChanges(); + + // With a modifier. + dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA, undefined, {shift: true}); + expect(testChipInput.add).not.toHaveBeenCalled(); + + // Without a modifier. + dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA); + expect(testChipInput.add).toHaveBeenCalledTimes(1); + }); + + it('should only allow modifiers from the `SeparatorKey.modifiers` array', () => { + spyOn(testChipInput, 'add'); + + chipInputDirective.separatorKeyCodes = [{keyCode: COMMA, modifiers: ['ctrlKey']}]; + fixture.detectChanges(); + + // Without a modifier. + dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA); + expect(testChipInput.add).not.toHaveBeenCalled(); + + // With a different modifier. + dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA, undefined, {shift: true}); + expect(testChipInput.add).not.toHaveBeenCalled(); + + // With the correct modifier. + dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA, undefined, {control: true}); + expect(testChipInput.add).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/src/material/chips/chip-input.ts b/src/material/chips/chip-input.ts index db6134d4d9fc..b7305e7fb88f 100644 --- a/src/material/chips/chip-input.ts +++ b/src/material/chips/chip-input.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {BACKSPACE, hasModifierKey} from '@angular/cdk/keycodes'; +import {BACKSPACE, hasModifierKey, ModifierKey} from '@angular/cdk/keycodes'; import { Directive, ElementRef, @@ -20,7 +20,7 @@ import { } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {MatFormField, MAT_FORM_FIELD} from '../form-field'; -import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './tokens'; +import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS, SeparatorKey} from './tokens'; import {MatChipGrid} from './chip-grid'; import {MatChipTextControl} from './chip-text-control'; @@ -97,7 +97,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { * Defaults to `[ENTER]`. */ @Input('matChipInputSeparatorKeyCodes') - separatorKeyCodes: readonly number[] | ReadonlySet; + separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet; /** Emitted when a chip is to be added. */ @Output('matChipInputTokenEnd') @@ -242,8 +242,33 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { } /** Checks whether a keycode is one of the configured separators. */ - private _isSeparatorKey(event: KeyboardEvent) { - return !hasModifierKey(event) && new Set(this.separatorKeyCodes).has(event.keyCode); + private _isSeparatorKey(event: KeyboardEvent): boolean { + if (!this.separatorKeyCodes) { + return false; + } + + for (const key of this.separatorKeyCodes) { + let keyCode: number; + let modifiers: readonly ModifierKey[] | null; + + if (typeof key === 'number') { + keyCode = key; + modifiers = null; + } else { + keyCode = key.keyCode; + modifiers = key.modifiers; + } + + const modifiersMatch = !modifiers?.length + ? !hasModifierKey(event) + : hasModifierKey(event, ...modifiers); + + if (keyCode === event.keyCode && modifiersMatch) { + return true; + } + } + + return false; } /** Gets the value to set on the `readonly` attribute. */ diff --git a/src/material/chips/tokens.ts b/src/material/chips/tokens.ts index a95ed2c0b132..431a1a7ed17f 100644 --- a/src/material/chips/tokens.ts +++ b/src/material/chips/tokens.ts @@ -6,13 +6,19 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ENTER} from '@angular/cdk/keycodes'; +import {ENTER, ModifierKey} from '@angular/cdk/keycodes'; import {InjectionToken} from '@angular/core'; +/** Key that can be used as a separator between chips. */ +export interface SeparatorKey { + keyCode: number; + modifiers: readonly ModifierKey[]; +} + /** Default options, for the chips module, that can be overridden. */ export interface MatChipsDefaultOptions { /** The list of key codes that will trigger a chipEnd event. */ - separatorKeyCodes: readonly number[] | ReadonlySet; + separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet; /** Whether icon indicators should be hidden for single-selection. */ hideSingleSelectionIndicator?: boolean;