Skip to content

Commit f9953d7

Browse files
committed
fix(tooltip): not closing if escape is pressed while trigger isn't focused
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 0d266e2 commit f9953d7

File tree

2 files changed

+35
-23
lines changed

2 files changed

+35
-23
lines changed

src/material/tooltip/tooltip.spec.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -640,9 +640,28 @@ describe('MatTooltip', () => {
640640
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
641641
}));
642642

643+
it('should hide when pressing escape', fakeAsync(() => {
644+
tooltipDirective.show();
645+
tick(0);
646+
fixture.detectChanges();
647+
tick(500);
648+
649+
expect(tooltipDirective._isTooltipVisible()).toBe(true);
650+
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
651+
652+
dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
653+
tick(0);
654+
fixture.detectChanges();
655+
tick(500);
656+
fixture.detectChanges();
657+
658+
expect(tooltipDirective._isTooltipVisible()).toBe(false);
659+
expect(overlayContainerElement.textContent).toBe('');
660+
}));
661+
643662
it('should not throw when pressing ESCAPE', fakeAsync(() => {
644663
expect(() => {
645-
dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
664+
dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
646665
fixture.detectChanges();
647666
}).not.toThrow();
648667

@@ -655,7 +674,7 @@ describe('MatTooltip', () => {
655674
tick(0);
656675
fixture.detectChanges();
657676

658-
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
677+
const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
659678
fixture.detectChanges();
660679
flush();
661680

@@ -669,7 +688,7 @@ describe('MatTooltip', () => {
669688

670689
const event = createKeyboardEvent('keydown', ESCAPE);
671690
Object.defineProperty(event, 'altKey', {get: () => true});
672-
dispatchEvent(buttonElement, event);
691+
dispatchEvent(document.body, event);
673692
fixture.detectChanges();
674693
flush();
675694

src/material/tooltip/tooltip.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
268268
this.touchGestures = _defaultOptions.touchGestures;
269269
}
270270
}
271-
272-
_ngZone.runOutsideAngular(() => {
273-
_elementRef.nativeElement.addEventListener('keydown', this._handleKeydown);
274-
});
275271
}
276272

277273
ngAfterViewInit() {
@@ -304,7 +300,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
304300
}
305301

306302
// Clean up the event listeners set in the constructor
307-
nativeElement.removeEventListener('keydown', this._handleKeydown);
308303
this._passiveListeners.forEach((listener, event) => {
309304
nativeElement.removeEventListener(event, listener, passiveListenerOptions);
310305
});
@@ -353,18 +348,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
353348
return !!this._tooltipInstance && this._tooltipInstance.isVisible();
354349
}
355350

356-
/**
357-
* Handles the keydown events on the host element.
358-
* Needs to be an arrow function so that we can use it in addEventListener.
359-
*/
360-
private _handleKeydown = (event: KeyboardEvent) => {
361-
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
362-
event.preventDefault();
363-
event.stopPropagation();
364-
this._ngZone.run(() => this.hide(0));
365-
}
366-
}
367-
368351
/** Create the overlay config and position strategy */
369352
private _createOverlay(): OverlayRef {
370353
if (this._overlayRef) {
@@ -392,7 +375,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
392375
}
393376
});
394377

395-
this._overlayRef = this._overlay.create({
378+
const overlayRef = this._overlayRef = this._overlay.create({
396379
direction: this._dir,
397380
positionStrategy: strategy,
398381
panelClass: TOOLTIP_PANEL_CLASS,
@@ -401,11 +384,21 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
401384

402385
this._updatePosition();
403386

404-
this._overlayRef.detachments()
387+
overlayRef.keydownEvents()
388+
.pipe(takeUntil(this._destroyed))
389+
.subscribe(event => {
390+
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
391+
event.preventDefault();
392+
event.stopPropagation();
393+
this._ngZone.run(() => this.hide(0));
394+
}
395+
});
396+
397+
overlayRef.detachments()
405398
.pipe(takeUntil(this._destroyed))
406399
.subscribe(() => this._detach());
407400

408-
return this._overlayRef;
401+
return overlayRef;
409402
}
410403

411404
/** Detaches the currently-attached tooltip. */

0 commit comments

Comments
 (0)