Skip to content

Commit 94f59f8

Browse files
crisbetojelbourn
authored andcommitted
fix(autocomplete): not closing when clicking outside while propagation is stopped
Fixes the autocomplete panel not closing if the user clicks outside on an element that stops propagation of the `click` event (e.g. a `mat-chip`). Fixes #17352.
1 parent a67cef6 commit 94f59f8

File tree

2 files changed

+32
-14
lines changed

2 files changed

+32
-14
lines changed

src/material/autocomplete/autocomplete-trigger.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -358,21 +358,23 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn
358358

359359
/** Stream of clicks outside of the autocomplete panel. */
360360
private _getOutsideClickStream(): Observable<any> {
361+
// Use capturing so we can close even if propagation is stopped.
362+
const eventOptions = {capture: true};
361363
return merge(
362-
fromEvent(this._document, 'click') as Observable<MouseEvent>,
363-
fromEvent(this._document, 'touchend') as Observable<TouchEvent>)
364-
.pipe(filter(event => {
365-
// If we're in the Shadow DOM, the event target will be the shadow root, so we have to
366-
// fall back to check the first element in the path of the click event.
367-
const clickTarget =
368-
(this._isInsideShadowRoot && event.composedPath ? event.composedPath()[0] :
369-
event.target) as HTMLElement;
370-
const formField = this._formField ? this._formField._elementRef.nativeElement : null;
371-
372-
return this._overlayAttached && clickTarget !== this._element.nativeElement &&
373-
(!formField || !formField.contains(clickTarget)) &&
374-
(!!this._overlayRef && !this._overlayRef.overlayElement.contains(clickTarget));
375-
}));
364+
fromEvent(this._document, 'click', eventOptions) as Observable<MouseEvent>,
365+
fromEvent(this._document, 'touchend', eventOptions) as Observable<TouchEvent>
366+
).pipe(filter(event => {
367+
// If we're in the Shadow DOM, the event target will be the shadow root, so we have to
368+
// fall back to check the first element in the path of the click event.
369+
const clickTarget =
370+
(this._isInsideShadowRoot && event.composedPath ? event.composedPath()[0] :
371+
event.target) as HTMLElement;
372+
const formField = this._formField ? this._formField._elementRef.nativeElement : null;
373+
374+
return this._overlayAttached && clickTarget !== this._element.nativeElement &&
375+
(!formField || !formField.contains(clickTarget)) &&
376+
(!!this._overlayRef && !this._overlayRef.overlayElement.contains(clickTarget));
377+
}));
376378
}
377379

378380
// Implemented as part of ControlValueAccessor.

src/material/autocomplete/autocomplete.spec.ts

+16
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,20 @@ describe('MatAutocomplete', () => {
188188
.toEqual('', `Expected clicking outside the panel to close the panel.`);
189189
}));
190190

191+
it('should close the panel when clicking away and propagation is stopped', fakeAsync(() => {
192+
const trigger = fixture.componentInstance.trigger;
193+
dispatchFakeEvent(input, 'focusin');
194+
fixture.detectChanges();
195+
zone.simulateZoneExit();
196+
197+
expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.');
198+
199+
fixture.nativeElement.querySelector('.stop-propagation').click();
200+
fixture.detectChanges();
201+
202+
expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.');
203+
}));
204+
191205
it('should close the panel when the user taps away on a touch device', fakeAsync(() => {
192206
dispatchFakeEvent(input, 'focus');
193207
fixture.detectChanges();
@@ -2584,6 +2598,8 @@ const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
25842598
<span>{{ state.code }}: {{ state.name }}</span>
25852599
</mat-option>
25862600
</mat-autocomplete>
2601+
2602+
<button class="stop-propagation" (click)="$event.stopPropagation()">Click me</button>
25872603
`;
25882604

25892605
@Component({template: SIMPLE_AUTOCOMPLETE_TEMPLATE})

0 commit comments

Comments
 (0)