diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index 9ff35385222d..0fd0c81567e1 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -241,8 +241,8 @@ export class MatChipGridChange { } // @public -export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { - constructor(...args: unknown[]); +export class MatChipInput implements MatChipTextControl, ControlValueAccessor, OnChanges, OnDestroy { + constructor(); addOnBlur: boolean; _blur(): void; readonly chipEnd: EventEmitter; @@ -283,9 +283,13 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { _onInput(): void; placeholder: string; readonly: boolean; + registerOnChange(fn: any): void; + registerOnTouched(fn: () => void): void; separatorKeyCodes: readonly number[] | ReadonlySet; // (undocumented) setDescribedByIds(ids: string[]): void; + setDisabledState(isDisabled: boolean): void; + writeValue(value: any): void; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) diff --git a/src/material/chips/chip-input.spec.ts b/src/material/chips/chip-input.spec.ts index efc62a96fd40..22df7cdcaeba 100644 --- a/src/material/chips/chip-input.spec.ts +++ b/src/material/chips/chip-input.spec.ts @@ -1,13 +1,13 @@ import {Directionality} from '@angular/cdk/bidi'; import {COMMA, ENTER, TAB} from '@angular/cdk/keycodes'; -import {PlatformModule} from '@angular/cdk/platform'; import { createKeyboardEvent, dispatchKeyboardEvent, dispatchEvent, } from '@angular/cdk/testing/private'; -import {Component, DebugElement, ViewChild} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, flush, waitForAsync} from '@angular/core/testing'; +import {Component, Provider, Type, ViewChild} from '@angular/core'; +import {FormControl, ReactiveFormsModule} from '@angular/forms'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {MatFormFieldModule} from '../form-field'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; @@ -22,301 +22,311 @@ import { import {MATERIAL_ANIMATIONS} from '../core'; describe('MatChipInput', () => { - let fixture: ComponentFixture; - let testChipInput: TestChipInput; - let inputDebugElement: DebugElement; - let inputNativeElement: HTMLInputElement; - let chipInputDirective: MatChipInput; let dir = 'ltr'; - beforeEach(waitForAsync(() => { + function createComponent(type: Type, providers: Provider[] = []) { TestBed.configureTestingModule({ - imports: [PlatformModule, MatChipsModule, MatFormFieldModule], + imports: [MatChipsModule, MatFormFieldModule, ReactiveFormsModule], providers: [ { provide: Directionality, - useFactory: () => { - return { - value: dir.toLowerCase(), - change: new Subject(), - }; - }, + useFactory: () => ({ + value: dir.toLowerCase(), + change: new Subject(), + }), }, {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, + ...providers, ], - declarations: [TestChipInput], + declarations: [type], }); - })); - beforeEach(waitForAsync(() => { - fixture = TestBed.createComponent(TestChipInput); - testChipInput = fixture.debugElement.componentInstance; + const fixture = TestBed.createComponent(type); fixture.detectChanges(); + return {fixture, input: fixture.nativeElement.querySelector('input') as HTMLInputElement}; + } - inputDebugElement = fixture.debugElement.query(By.directive(MatChipInput))!; - chipInputDirective = inputDebugElement.injector.get(MatChipInput); - inputNativeElement = inputDebugElement.nativeElement; - })); + it('emits the (chipEnd) on enter keyup', () => { + const {fixture, input} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - describe('basic behavior', () => { - it('emits the (chipEnd) on enter keyup', () => { - spyOn(testChipInput, 'add'); + dispatchKeyboardEvent(input, 'keydown', ENTER); + expect(fixture.componentInstance.add).toHaveBeenCalled(); + }); - dispatchKeyboardEvent(inputNativeElement, 'keydown', ENTER); - expect(testChipInput.add).toHaveBeenCalled(); - }); + it('should have a default id', () => { + const {input} = createComponent(TestChipInput); + expect(input.getAttribute('id')).toBeTruthy(); + }); - it('should have a default id', () => { - expect(inputNativeElement.getAttribute('id')).toBeTruthy(); - }); + it('should allow binding to the `placeholder` input', () => { + const {fixture, input} = createComponent(TestChipInput); + expect(input.hasAttribute('placeholder')).toBe(false); - it('should allow binding to the `placeholder` input', () => { - expect(inputNativeElement.hasAttribute('placeholder')).toBe(false); + fixture.componentInstance.placeholder = 'bound placeholder'; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - testChipInput.placeholder = 'bound placeholder'; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + expect(input.getAttribute('placeholder')).toBe('bound placeholder'); + }); - expect(inputNativeElement.getAttribute('placeholder')).toBe('bound placeholder'); - }); + it('should become disabled if the list is disabled', () => { + const {fixture, input} = createComponent(TestChipInput); + expect(input.hasAttribute('disabled')).toBe(false); + expect(fixture.componentInstance.chipInput.disabled).toBe(false); - it('should become disabled if the list is disabled', () => { - expect(inputNativeElement.hasAttribute('disabled')).toBe(false); - expect(chipInputDirective.disabled).toBe(false); + fixture.componentInstance.chipGridInstance.disabled = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - fixture.componentInstance.chipGridInstance.disabled = true; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + expect(input.disabled).toBe(true); + expect(fixture.componentInstance.chipInput.disabled).toBe(true); + }); - expect(inputNativeElement.disabled).toBe(true); - expect(chipInputDirective.disabled).toBe(true); - }); + it('should be able to set an input as being disabled and interactive', fakeAsync(() => { + const {fixture, input} = createComponent(TestChipInput); + fixture.componentInstance.chipGridInstance.disabled = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - it('should be able to set an input as being disabled and interactive', fakeAsync(() => { - fixture.componentInstance.chipGridInstance.disabled = true; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + expect(input.disabled).toBe(true); + expect(input.readOnly).toBe(false); + expect(input.hasAttribute('aria-disabled')).toBe(false); - expect(inputNativeElement.disabled).toBe(true); - expect(inputNativeElement.readOnly).toBe(false); - expect(inputNativeElement.hasAttribute('aria-disabled')).toBe(false); + fixture.componentInstance.disabledInteractive = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - fixture.componentInstance.disabledInteractive = true; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + expect(input.disabled).toBe(false); + expect(input.readOnly).toBe(true); + expect(input.getAttribute('aria-disabled')).toBe('true'); + })); - expect(inputNativeElement.disabled).toBe(false); - expect(inputNativeElement.readOnly).toBe(true); - expect(inputNativeElement.getAttribute('aria-disabled')).toBe('true'); - })); + it('should be able to set an input as being disabled and interactive when using the reactive forms module', fakeAsync(() => { + const {fixture, input} = createComponent(ChipInputWithFormControl); + fixture.componentInstance.control.disable(); + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - it('should be aria-required if the list is required', () => { - expect(inputNativeElement.hasAttribute('aria-required')).toBe(false); + expect(input.disabled).toBe(true); + expect(input.readOnly).toBe(false); + expect(input.hasAttribute('aria-disabled')).toBe(false); - fixture.componentInstance.required = true; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + fixture.componentInstance.disabledInteractive = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - expect(inputNativeElement.getAttribute('aria-required')).toBe('true'); - }); + expect(input.disabled).toBe(false); + expect(input.readOnly).toBe(true); + expect(input.getAttribute('aria-disabled')).toBe('true'); + })); - it('should be required if the list is required', () => { - expect(inputNativeElement.hasAttribute('required')).toBe(false); + it('should be aria-required if the list is required', () => { + const {fixture, input} = createComponent(TestChipInput); + expect(input.hasAttribute('aria-required')).toBe(false); - fixture.componentInstance.required = true; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + fixture.componentInstance.required = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - expect(inputNativeElement.getAttribute('required')).toBe('true'); - }); + expect(input.getAttribute('aria-required')).toBe('true'); + }); - it('should allow focus to escape when tabbing forwards', fakeAsync(() => { - const gridElement: HTMLElement = fixture.nativeElement.querySelector('mat-chip-grid'); + it('should be required if the list is required', () => { + const {fixture, input} = createComponent(TestChipInput); + expect(input.hasAttribute('required')).toBe(false); - expect(gridElement.getAttribute('tabindex')).toBe('0'); + fixture.componentInstance.required = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); - dispatchKeyboardEvent(gridElement, 'keydown', TAB); - fixture.detectChanges(); + expect(input.getAttribute('required')).toBe('true'); + }); - expect(gridElement.getAttribute('tabindex')) - .withContext('Expected tabIndex to be set to -1 temporarily.') - .toBe('-1'); + it('should allow focus to escape when tabbing forwards', fakeAsync(() => { + const {fixture} = createComponent(TestChipInput); + const gridElement: HTMLElement = fixture.nativeElement.querySelector('mat-chip-grid'); - flush(); - fixture.detectChanges(); + expect(gridElement.getAttribute('tabindex')).toBe('0'); - expect(gridElement.getAttribute('tabindex')) - .withContext('Expected tabIndex to be reset back to 0') - .toBe('0'); - })); + dispatchKeyboardEvent(gridElement, 'keydown', TAB); + fixture.detectChanges(); - it('should set input styling classes', () => { - expect(inputNativeElement.classList).toContain('mat-mdc-input-element'); - expect(inputNativeElement.classList).toContain('mat-mdc-form-field-input-control'); - expect(inputNativeElement.classList).toContain('mat-mdc-chip-input'); - expect(inputNativeElement.classList).toContain('mdc-text-field__input'); - }); + expect(gridElement.getAttribute('tabindex')) + .withContext('Expected tabIndex to be set to -1 temporarily.') + .toBe('-1'); - it('should set `aria-describedby` to the id of the mat-hint', () => { - expect(inputNativeElement.getAttribute('aria-describedby')).toBeNull(); + flush(); + fixture.detectChanges(); - fixture.componentInstance.hint = 'test'; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - const hint = fixture.debugElement.query(By.css('mat-hint')).nativeElement; + expect(gridElement.getAttribute('tabindex')) + .withContext('Expected tabIndex to be reset back to 0') + .toBe('0'); + })); - expect(inputNativeElement.getAttribute('aria-describedby')).toBe(hint.getAttribute('id')); - expect(inputNativeElement.getAttribute('aria-describedby')).toMatch(/^mat-mdc-hint-\w+\d+$/); - }); + it('should set input styling classes', () => { + const {input} = createComponent(TestChipInput); + expect(input.classList).toContain('mat-mdc-input-element'); + expect(input.classList).toContain('mat-mdc-form-field-input-control'); + expect(input.classList).toContain('mat-mdc-chip-input'); + expect(input.classList).toContain('mdc-text-field__input'); + }); - it('should support user binding to `aria-describedby`', () => { - inputNativeElement.setAttribute('aria-describedby', 'test'); - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); + it('should set `aria-describedby` to the id of the mat-hint', () => { + const {fixture, input} = createComponent(TestChipInput); + expect(input.getAttribute('aria-describedby')).toBeNull(); - expect(inputNativeElement.getAttribute('aria-describedby')).toBe('test'); - }); + fixture.componentInstance.hint = 'test'; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + const hint = fixture.debugElement.query(By.css('mat-hint')).nativeElement; - it('should preserve aria-describedby set directly in the DOM', fakeAsync(() => { - inputNativeElement.setAttribute('aria-describedby', 'custom'); - fixture.componentInstance.hint = 'test'; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - const hint = fixture.debugElement.query(By.css('mat-hint')).nativeElement; + expect(input.getAttribute('aria-describedby')).toBe(hint.getAttribute('id')); + expect(input.getAttribute('aria-describedby')).toMatch(/^mat-mdc-hint-\w+\d+$/); + }); - expect(inputNativeElement.getAttribute('aria-describedby')).toBe( - `${hint.getAttribute('id')} custom`, - ); - })); + it('should support user binding to `aria-describedby`', () => { + const {fixture, input} = createComponent(TestChipInput); + input.setAttribute('aria-describedby', 'test'); + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expect(input.getAttribute('aria-describedby')).toBe('test'); }); + it('should preserve aria-describedby set directly in the DOM', fakeAsync(() => { + const {fixture, input} = createComponent(TestChipInput); + input.setAttribute('aria-describedby', 'custom'); + fixture.componentInstance.hint = 'test'; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + const hint = fixture.debugElement.query(By.css('mat-hint')).nativeElement; + + expect(input.getAttribute('aria-describedby')).toBe(`${hint.getAttribute('id')} custom`); + })); + describe('[addOnBlur]', () => { it('allows (chipEnd) when true', () => { - spyOn(testChipInput, 'add'); + const {fixture} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - testChipInput.addOnBlur = true; + fixture.componentInstance.addOnBlur = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - chipInputDirective._blur(); - expect(testChipInput.add).toHaveBeenCalled(); + fixture.componentInstance.chipInput._blur(); + expect(fixture.componentInstance.add).toHaveBeenCalled(); }); it('disallows (chipEnd) when false', () => { - spyOn(testChipInput, 'add'); + const {fixture} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - testChipInput.addOnBlur = false; + fixture.componentInstance.addOnBlur = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - chipInputDirective._blur(); - expect(testChipInput.add).not.toHaveBeenCalled(); + fixture.componentInstance.chipInput._blur(); + expect(fixture.componentInstance.add).not.toHaveBeenCalled(); }); }); describe('[separatorKeyCodes]', () => { it('does not emit (chipEnd) when a non-separator key is pressed', () => { - spyOn(testChipInput, 'add'); + const {fixture, input} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - chipInputDirective.separatorKeyCodes = [COMMA]; + fixture.componentInstance.chipInput.separatorKeyCodes = [COMMA]; fixture.detectChanges(); - dispatchKeyboardEvent(inputNativeElement, 'keydown', ENTER); - expect(testChipInput.add).not.toHaveBeenCalled(); + dispatchKeyboardEvent(input, 'keydown', ENTER); + expect(fixture.componentInstance.add).not.toHaveBeenCalled(); }); it('emits (chipEnd) when a custom separator keys is pressed', () => { - spyOn(testChipInput, 'add'); + const {fixture, input} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - chipInputDirective.separatorKeyCodes = [COMMA]; + fixture.componentInstance.chipInput.separatorKeyCodes = [COMMA]; fixture.detectChanges(); - dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA); - expect(testChipInput.add).toHaveBeenCalled(); + dispatchKeyboardEvent(input, 'keydown', COMMA); + expect(fixture.componentInstance.add).toHaveBeenCalled(); }); it('emits accepts the custom separator keys in a Set', () => { - spyOn(testChipInput, 'add'); + const {fixture, input} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - chipInputDirective.separatorKeyCodes = new Set([COMMA]); + fixture.componentInstance.chipInput.separatorKeyCodes = new Set([COMMA]); fixture.detectChanges(); - dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA); - expect(testChipInput.add).toHaveBeenCalled(); + dispatchKeyboardEvent(input, 'keydown', COMMA); + expect(fixture.componentInstance.add).toHaveBeenCalled(); }); it('emits (chipEnd) when the separator keys are configured globally', () => { - fixture.destroy(); - - TestBed.resetTestingModule().configureTestingModule({ - imports: [MatChipsModule, MatFormFieldModule, PlatformModule], - declarations: [TestChipInput], - providers: [ - { - provide: MAT_CHIPS_DEFAULT_OPTIONS, - useValue: {separatorKeyCodes: [COMMA]} as MatChipsDefaultOptions, - }, - {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, - ], - }); - - fixture = TestBed.createComponent(TestChipInput); - testChipInput = fixture.debugElement.componentInstance; + const {fixture, input} = createComponent(TestChipInput, [ + { + provide: MAT_CHIPS_DEFAULT_OPTIONS, + useValue: {separatorKeyCodes: [COMMA]} as MatChipsDefaultOptions, + }, + ]); fixture.detectChanges(); - inputDebugElement = fixture.debugElement.query(By.directive(MatChipInput))!; - chipInputDirective = inputDebugElement.injector.get(MatChipInput); - inputNativeElement = inputDebugElement.nativeElement; - - spyOn(testChipInput, 'add'); + spyOn(fixture.componentInstance, 'add'); fixture.detectChanges(); - dispatchKeyboardEvent(inputNativeElement, 'keydown', COMMA); - expect(testChipInput.add).toHaveBeenCalled(); + dispatchKeyboardEvent(input, 'keydown', COMMA); + expect(fixture.componentInstance.add).toHaveBeenCalled(); }); it('should not emit the chipEnd event if a separator is pressed with a modifier key', () => { - spyOn(testChipInput, 'add'); + const {fixture, input} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - chipInputDirective.separatorKeyCodes = [ENTER]; + fixture.componentInstance.chipInput.separatorKeyCodes = [ENTER]; fixture.detectChanges(); - dispatchKeyboardEvent(inputNativeElement, 'keydown', ENTER, undefined, {shift: true}); - expect(testChipInput.add).not.toHaveBeenCalled(); + dispatchKeyboardEvent(input, 'keydown', ENTER, undefined, {shift: true}); + expect(fixture.componentInstance.add).not.toHaveBeenCalled(); }); it('should set aria-describedby correctly when a non-empty list of ids is passed to setDescribedByIds', fakeAsync(() => { + const {fixture, input} = createComponent(TestChipInput); const ids = ['a', 'b', 'c']; - testChipInput.chipGridInstance.setDescribedByIds(ids); + fixture.componentInstance.chipGridInstance.setDescribedByIds(ids); flush(); fixture.detectChanges(); - expect(inputNativeElement.getAttribute('aria-describedby')).toEqual('a b c'); + expect(input.getAttribute('aria-describedby')).toEqual('a b c'); })); it('should set aria-describedby correctly when an empty list of ids is passed to setDescribedByIds', fakeAsync(() => { + const {fixture, input} = createComponent(TestChipInput); const ids: string[] = []; - testChipInput.chipGridInstance.setDescribedByIds(ids); + fixture.componentInstance.chipGridInstance.setDescribedByIds(ids); flush(); fixture.detectChanges(); - expect(inputNativeElement.getAttribute('aria-describedby')).toBeNull(); + expect(input.getAttribute('aria-describedby')).toBeNull(); })); it('should not emit chipEnd if the key is repeated', () => { - spyOn(testChipInput, 'add'); + const {fixture, input} = createComponent(TestChipInput); + spyOn(fixture.componentInstance, 'add'); - chipInputDirective.separatorKeyCodes = [COMMA]; + fixture.componentInstance.chipInput.separatorKeyCodes = [COMMA]; fixture.detectChanges(); const event = createKeyboardEvent('keydown', COMMA); Object.defineProperty(event, 'repeat', {get: () => true}); - dispatchEvent(inputNativeElement, event); + dispatchEvent(input, event); fixture.detectChanges(); - expect(testChipInput.add).not.toHaveBeenCalled(); + expect(fixture.componentInstance.add).not.toHaveBeenCalled(); }); }); }); @@ -339,6 +349,7 @@ describe('MatChipInput', () => { }) class TestChipInput { @ViewChild(MatChipGrid) chipGridInstance: MatChipGrid; + @ViewChild(MatChipInput) chipInput: MatChipInput; addOnBlur: boolean = false; placeholder = ''; required = false; @@ -347,3 +358,23 @@ class TestChipInput { add(_: MatChipInputEvent) {} } + +@Component({ + template: ` + + + Hello + + + + `, + standalone: false, +}) +class ChipInputWithFormControl { + @ViewChild(MatChipInput) chipInput: MatChipInput; + disabledInteractive = false; + control = new FormControl(); +} diff --git a/src/material/chips/chip-input.ts b/src/material/chips/chip-input.ts index db6134d4d9fc..5154fae6d25c 100644 --- a/src/material/chips/chip-input.ts +++ b/src/material/chips/chip-input.ts @@ -23,6 +23,7 @@ import {MatFormField, MAT_FORM_FIELD} from '../form-field'; import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './tokens'; import {MatChipGrid} from './chip-grid'; import {MatChipTextControl} from './chip-text-control'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; /** Represents an input event on a `matChipInput`. */ export interface MatChipInputEvent { @@ -65,8 +66,22 @@ export interface MatChipInputEvent { '[attr.readonly]': '_getReadonlyAttribute()', '[attr.required]': '_chipGrid && _chipGrid.required || null', }, + providers: [ + { + // Note: we primarily provide the chip input as a CVA in order to have full control over + // how some attributes like `disabled` are set. This is necessary to properly support + // `disabledInteractive`. + provide: NG_VALUE_ACCESSOR, + useExisting: MatChipInput, + multi: true, + }, + ], }) -export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { +export class MatChipInput + implements MatChipTextControl, ControlValueAccessor, OnChanges, OnDestroy +{ + private _onTouched: (() => void) | undefined; + private _onChange: ((value: any) => void) | undefined; protected _elementRef = inject>(ElementRef); /** Whether the control is focused. */ @@ -112,7 +127,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { /** Whether the input is disabled. */ @Input({transform: booleanAttribute}) get disabled(): boolean { - return this._disabled || (this._chipGrid && this._chipGrid.disabled); + return this._disabled || !!this._chipGrid?.disabled; } set disabled(value: boolean) { this._disabled = value; @@ -135,8 +150,6 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { /** The native input element to which this directive is attached. */ readonly inputElement!: HTMLInputElement; - constructor(...args: unknown[]); - constructor() { const defaultOptions = inject(MAT_CHIPS_DEFAULT_OPTIONS); const formField = inject(MAT_FORM_FIELD, {optional: true}); @@ -182,6 +195,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { if (!this._chipGrid.focused) { this._chipGrid._blur(); } + this._onTouched?.(); this._chipGrid.stateChanges.next(); } @@ -206,6 +220,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { _onInput() { // Let chip list know whenever the value changes. this._chipGrid.stateChanges.next(); + this._onChange?.(this.inputElement.value); } /** Focuses the input. */ @@ -216,6 +231,7 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { /** Clears the input */ clear(): void { this.inputElement.value = ''; + this._onChange?.(''); } /** @@ -241,6 +257,38 @@ export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { } } + /** + * Implemented as a part of ControlValueAccessor. + * @docs-private + */ + writeValue(value: any): void { + this.inputElement.value = value; + } + + /** + * Implemented as a part of ControlValueAccessor. + * @docs-private + */ + registerOnChange(fn: any): void { + this._onChange = fn; + } + + /** + * Implemented as a part of ControlValueAccessor. + * @docs-private + */ + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + /** + * Implemented as a part of ControlValueAccessor. + * @docs-private + */ + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + /** Checks whether a keycode is one of the configured separators. */ private _isSeparatorKey(event: KeyboardEvent) { return !hasModifierKey(event) && new Set(this.separatorKeyCodes).has(event.keyCode);