Skip to content

Commit c69ba1f

Browse files
committed
feat(material/chips): allow for modifiers to be specified on separator keys
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 b5f1f88 commit c69ba1f

File tree

4 files changed

+78
-8
lines changed

4 files changed

+78
-8
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: 25 additions & 4 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')
@@ -243,7 +243,28 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy {
243243

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

249270
/** 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)