Skip to content

Commit 08aed71

Browse files
authored
fix(material/tooltip): not closing if escape is pressed while trigger isn't focused (#14434)
Currently the tooltip's `keydown` handler is on the trigger, which means that it won't fire if the trigger doesn't have focus. This could happen when a tooltip was opened by hovering over the trigger. These changes use the `OverlayKeyboardDispatcher` to handle the events instead. Fixes #14278.
1 parent 177d904 commit 08aed71

File tree

3 files changed

+55
-23
lines changed

3 files changed

+55
-23
lines changed

src/material-experimental/mdc-tooltip/tooltip.spec.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -697,9 +697,28 @@ describe('MDC-based MatTooltip', () => {
697697
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
698698
}));
699699

700+
it('should hide when pressing escape', fakeAsync(() => {
701+
tooltipDirective.show();
702+
tick(0);
703+
fixture.detectChanges();
704+
tick(500);
705+
706+
expect(tooltipDirective._isTooltipVisible()).toBe(true);
707+
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
708+
709+
dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
710+
tick(0);
711+
fixture.detectChanges();
712+
tick(500);
713+
fixture.detectChanges();
714+
715+
expect(tooltipDirective._isTooltipVisible()).toBe(false);
716+
expect(overlayContainerElement.textContent).toBe('');
717+
}));
718+
700719
it('should not throw when pressing ESCAPE', fakeAsync(() => {
701720
expect(() => {
702-
dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
721+
dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
703722
fixture.detectChanges();
704723
}).not.toThrow();
705724

@@ -712,7 +731,7 @@ describe('MDC-based MatTooltip', () => {
712731
tick(0);
713732
fixture.detectChanges();
714733

715-
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
734+
const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
716735
fixture.detectChanges();
717736
flush();
718737

@@ -725,7 +744,7 @@ describe('MDC-based MatTooltip', () => {
725744
fixture.detectChanges();
726745

727746
const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true});
728-
dispatchEvent(buttonElement, event);
747+
dispatchEvent(document.body, event);
729748
fixture.detectChanges();
730749
flush();
731750

src/material/tooltip/tooltip.spec.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -693,9 +693,28 @@ describe('MatTooltip', () => {
693693
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
694694
}));
695695

696+
it('should hide when pressing escape', fakeAsync(() => {
697+
tooltipDirective.show();
698+
tick(0);
699+
fixture.detectChanges();
700+
tick(500);
701+
702+
expect(tooltipDirective._isTooltipVisible()).toBe(true);
703+
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
704+
705+
dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
706+
tick(0);
707+
fixture.detectChanges();
708+
tick(500);
709+
fixture.detectChanges();
710+
711+
expect(tooltipDirective._isTooltipVisible()).toBe(false);
712+
expect(overlayContainerElement.textContent).toBe('');
713+
}));
714+
696715
it('should not throw when pressing ESCAPE', fakeAsync(() => {
697716
expect(() => {
698-
dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
717+
dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
699718
fixture.detectChanges();
700719
}).not.toThrow();
701720

@@ -708,7 +727,7 @@ describe('MatTooltip', () => {
708727
tick(0);
709728
fixture.detectChanges();
710729

711-
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
730+
const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
712731
fixture.detectChanges();
713732
flush();
714733

@@ -721,7 +740,7 @@ describe('MatTooltip', () => {
721740
fixture.detectChanges();
722741

723742
const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true});
724-
dispatchEvent(buttonElement, event);
743+
dispatchEvent(document.body, event);
725744
fixture.detectChanges();
726745
flush();
727746

src/material/tooltip/tooltip.ts

+11-17
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,6 @@ export abstract class _MatTooltipBase<T extends _TooltipComponentBase>
314314
this._updatePosition(this._overlayRef);
315315
}
316316
});
317-
318-
_ngZone.runOutsideAngular(() => {
319-
_elementRef.nativeElement.addEventListener('keydown', this._handleKeydown);
320-
});
321317
}
322318

323319
ngAfterViewInit() {
@@ -352,7 +348,6 @@ export abstract class _MatTooltipBase<T extends _TooltipComponentBase>
352348
}
353349

354350
// Clean up the event listeners set in the constructor
355-
nativeElement.removeEventListener('keydown', this._handleKeydown);
356351
this._passiveListeners.forEach(([event, listener]) => {
357352
nativeElement.removeEventListener(event, listener, passiveListenerOptions);
358353
});
@@ -408,18 +403,6 @@ export abstract class _MatTooltipBase<T extends _TooltipComponentBase>
408403
return !!this._tooltipInstance && this._tooltipInstance.isVisible();
409404
}
410405

411-
/**
412-
* Handles the keydown events on the host element.
413-
* Needs to be an arrow function so that we can use it in addEventListener.
414-
*/
415-
private _handleKeydown = (event: KeyboardEvent) => {
416-
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
417-
event.preventDefault();
418-
event.stopPropagation();
419-
this._ngZone.run(() => this.hide(0));
420-
}
421-
};
422-
423406
/** Create the overlay config and position strategy */
424407
private _createOverlay(): OverlayRef {
425408
if (this._overlayRef) {
@@ -470,6 +453,17 @@ export abstract class _MatTooltipBase<T extends _TooltipComponentBase>
470453
.pipe(takeUntil(this._destroyed))
471454
.subscribe(() => this._tooltipInstance?._handleBodyInteraction());
472455

456+
this._overlayRef
457+
.keydownEvents()
458+
.pipe(takeUntil(this._destroyed))
459+
.subscribe(event => {
460+
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
461+
event.preventDefault();
462+
event.stopPropagation();
463+
this._ngZone.run(() => this.hide(0));
464+
}
465+
});
466+
473467
return this._overlayRef;
474468
}
475469

0 commit comments

Comments
 (0)