Skip to content

Commit ef70029

Browse files
authored
feat(material/chips): allow for modifiers to be specified on separator keys (angular#31914)
Currently we ignore separator keys if any modifier is pressed, but in some cases that might not be desirable. These changes add an option to specify a separator key with modifiers so that users can customize the behavior.
1 parent bb305d3 commit ef70029

File tree

4 files changed

+83
-9
lines changed

4 files changed

+83
-9
lines changed

goldens/material/chips/index.api.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import * as i0 from '@angular/core';
1919
import * as i2 from '@angular/cdk/bidi';
2020
import { InjectionToken } from '@angular/core';
2121
import { Injector } from '@angular/core';
22+
import { ModifierKey } from '@angular/cdk/keycodes';
2223
import { NgControl } from '@angular/forms';
2324
import { NgForm } from '@angular/forms';
2425
import { NgZone } from '@angular/core';
@@ -304,7 +305,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy {
304305
_onInput(): void;
305306
placeholder: string;
306307
readonly: boolean;
307-
separatorKeyCodes: readonly number[] | ReadonlySet<number>;
308+
separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet<number | SeparatorKey>;
308309
// (undocumented)
309310
setDescribedByIds(ids: string[]): void;
310311
// (undocumented)
@@ -472,7 +473,7 @@ export class MatChipRow extends MatChip implements AfterViewInit {
472473
export interface MatChipsDefaultOptions {
473474
hideSingleSelectionIndicator?: boolean;
474475
inputDisabledInteractive?: boolean;
475-
separatorKeyCodes: readonly number[] | ReadonlySet<number>;
476+
separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet<number | SeparatorKey>;
476477
}
477478

478479
// @public
@@ -564,6 +565,14 @@ export class MatChipTrailingIcon extends MatChipContent {
564565
static ɵfac: i0.ɵɵFactoryDeclaration<MatChipTrailingIcon, never>;
565566
}
566567

568+
// @public
569+
export interface SeparatorKey {
570+
// (undocumented)
571+
keyCode: number;
572+
// (undocumented)
573+
modifiers: readonly ModifierKey[];
574+
}
575+
567576
// (No @packageDocumentation comment for this package)
568577

569578
```

src/material/chips/chip-input.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,40 @@ describe('MatChipInput', () => {
303303

304304
expect(testChipInput.add).not.toHaveBeenCalled();
305305
});
306+
307+
it('should ignore modifier keys when `SeparatorKey.modifiers` is empty', () => {
308+
spyOn(testChipInput, 'add');
309+
310+
chipInputDirective.separatorKeyCodes = [{keyCode: COMMA, modifiers: []}];
311+
fixture.detectChanges();
312+
313+
// With a modifier.
314+
dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA, undefined, {shift: true});
315+
expect(testChipInput.add).not.toHaveBeenCalled();
316+
317+
// Without a modifier.
318+
dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA);
319+
expect(testChipInput.add).toHaveBeenCalledTimes(1);
320+
});
321+
322+
it('should only allow modifiers from the `SeparatorKey.modifiers` array', () => {
323+
spyOn(testChipInput, 'add');
324+
325+
chipInputDirective.separatorKeyCodes = [{keyCode: COMMA, modifiers: ['ctrlKey']}];
326+
fixture.detectChanges();
327+
328+
// Without a modifier.
329+
dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA);
330+
expect(testChipInput.add).not.toHaveBeenCalled();
331+
332+
// With a different modifier.
333+
dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA, undefined, {shift: true});
334+
expect(testChipInput.add).not.toHaveBeenCalled();
335+
336+
// With the correct modifier.
337+
dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA, undefined, {control: true});
338+
expect(testChipInput.add).toHaveBeenCalledTimes(1);
339+
});
306340
});
307341
});
308342

src/material/chips/chip-input.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {BACKSPACE, hasModifierKey} from '@angular/cdk/keycodes';
9+
import {BACKSPACE, hasModifierKey, ModifierKey} from '@angular/cdk/keycodes';
1010
import {
1111
Directive,
1212
ElementRef,
@@ -20,7 +20,7 @@ import {
2020
} from '@angular/core';
2121
import {_IdGenerator} from '@angular/cdk/a11y';
2222
import {MatFormField, MAT_FORM_FIELD} from '../form-field';
23-
import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './tokens';
23+
import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS, SeparatorKey} from './tokens';
2424
import {MatChipGrid} from './chip-grid';
2525
import {MatChipTextControl} from './chip-text-control';
2626

@@ -97,7 +97,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy {
9797
* Defaults to `[ENTER]`.
9898
*/
9999
@Input('matChipInputSeparatorKeyCodes')
100-
separatorKeyCodes: readonly number[] | ReadonlySet<number>;
100+
separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet<number | SeparatorKey>;
101101

102102
/** Emitted when a chip is to be added. */
103103
@Output('matChipInputTokenEnd')
@@ -242,8 +242,33 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy {
242242
}
243243

244244
/** Checks whether a keycode is one of the configured separators. */
245-
private _isSeparatorKey(event: KeyboardEvent) {
246-
return !hasModifierKey(event) && new Set(this.separatorKeyCodes).has(event.keyCode);
245+
private _isSeparatorKey(event: KeyboardEvent): boolean {
246+
if (!this.separatorKeyCodes) {
247+
return false;
248+
}
249+
250+
for (const key of this.separatorKeyCodes) {
251+
let keyCode: number;
252+
let modifiers: readonly ModifierKey[] | null;
253+
254+
if (typeof key === 'number') {
255+
keyCode = key;
256+
modifiers = null;
257+
} else {
258+
keyCode = key.keyCode;
259+
modifiers = key.modifiers;
260+
}
261+
262+
const modifiersMatch = !modifiers?.length
263+
? !hasModifierKey(event)
264+
: hasModifierKey(event, ...modifiers);
265+
266+
if (keyCode === event.keyCode && modifiersMatch) {
267+
return true;
268+
}
269+
}
270+
271+
return false;
247272
}
248273

249274
/** Gets the value to set on the `readonly` attribute. */

src/material/chips/tokens.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {ENTER} from '@angular/cdk/keycodes';
9+
import {ENTER, ModifierKey} from '@angular/cdk/keycodes';
1010
import {InjectionToken} from '@angular/core';
1111

12+
/** Key that can be used as a separator between chips. */
13+
export interface SeparatorKey {
14+
keyCode: number;
15+
modifiers: readonly ModifierKey[];
16+
}
17+
1218
/** Default options, for the chips module, that can be overridden. */
1319
export interface MatChipsDefaultOptions {
1420
/** The list of key codes that will trigger a chipEnd event. */
15-
separatorKeyCodes: readonly number[] | ReadonlySet<number>;
21+
separatorKeyCodes: readonly (number | SeparatorKey)[] | ReadonlySet<number | SeparatorKey>;
1622

1723
/** Whether icon indicators should be hidden for single-selection. */
1824
hideSingleSelectionIndicator?: boolean;

0 commit comments

Comments
 (0)