Skip to content

Commit 66a4c3e

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 14c4dba commit 66a4c3e

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
@@ -282,10 +282,6 @@ export class MatTooltip implements OnDestroy, OnInit {
282282
_ngZone.run(() => this.show());
283283
}
284284
});
285-
286-
_ngZone.runOutsideAngular(() => {
287-
_elementRef.nativeElement.addEventListener('keydown', this._handleKeydown);
288-
});
289285
}
290286

291287
/**
@@ -310,7 +306,6 @@ export class MatTooltip implements OnDestroy, OnInit {
310306
}
311307

312308
// Clean up the event listeners set in the constructor
313-
nativeElement.removeEventListener('keydown', this._handleKeydown);
314309
this._passiveListeners.forEach((listener, event) => {
315310
nativeElement.removeEventListener(event, listener, passiveListenerOptions);
316311
});
@@ -360,18 +355,6 @@ export class MatTooltip implements OnDestroy, OnInit {
360355
return !!this._tooltipInstance && this._tooltipInstance.isVisible();
361356
}
362357

363-
/**
364-
* Handles the keydown events on the host element.
365-
* Needs to be an arrow function so that we can use it in addEventListener.
366-
*/
367-
private _handleKeydown = (event: KeyboardEvent) => {
368-
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
369-
event.preventDefault();
370-
event.stopPropagation();
371-
this._ngZone.run(() => this.hide(0));
372-
}
373-
}
374-
375358
/** Create the overlay config and position strategy */
376359
private _createOverlay(): OverlayRef {
377360
if (this._overlayRef) {
@@ -399,7 +382,7 @@ export class MatTooltip implements OnDestroy, OnInit {
399382
}
400383
});
401384

402-
this._overlayRef = this._overlay.create({
385+
const overlayRef = this._overlayRef = this._overlay.create({
403386
direction: this._dir,
404387
positionStrategy: strategy,
405388
panelClass: TOOLTIP_PANEL_CLASS,
@@ -408,11 +391,21 @@ export class MatTooltip implements OnDestroy, OnInit {
408391

409392
this._updatePosition();
410393

411-
this._overlayRef.detachments()
394+
overlayRef.keydownEvents()
395+
.pipe(takeUntil(this._destroyed))
396+
.subscribe(event => {
397+
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
398+
event.preventDefault();
399+
event.stopPropagation();
400+
this._ngZone.run(() => this.hide(0));
401+
}
402+
});
403+
404+
overlayRef.detachments()
412405
.pipe(takeUntil(this._destroyed))
413406
.subscribe(() => this._detach());
414407

415-
return this._overlayRef;
408+
return overlayRef;
416409
}
417410

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

0 commit comments

Comments
 (0)