Skip to content

Commit 1489a3b

Browse files
committed
fix(material/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 71b7b15 commit 1489a3b

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
@@ -639,9 +639,28 @@ describe('MatTooltip', () => {
639639
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
640640
}));
641641

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

@@ -654,7 +673,7 @@ describe('MatTooltip', () => {
654673
tick(0);
655674
fixture.detectChanges();
656675

657-
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ESCAPE);
676+
const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
658677
fixture.detectChanges();
659678
flush();
660679

@@ -667,7 +686,7 @@ describe('MatTooltip', () => {
667686
fixture.detectChanges();
668687

669688
const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true});
670-
dispatchEvent(buttonElement, event);
689+
dispatchEvent(document.body, event);
671690
fixture.detectChanges();
672691
flush();
673692

src/material/tooltip/tooltip.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
286286
this.touchGestures = _defaultOptions.touchGestures;
287287
}
288288
}
289-
290-
_ngZone.runOutsideAngular(() => {
291-
_elementRef.nativeElement.addEventListener('keydown', this._handleKeydown);
292-
});
293289
}
294290

295291
ngAfterViewInit() {
@@ -323,7 +319,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
323319
}
324320

325321
// Clean up the event listeners set in the constructor
326-
nativeElement.removeEventListener('keydown', this._handleKeydown);
327322
this._passiveListeners.forEach(([event, listener]) => {
328323
nativeElement.removeEventListener(event, listener, passiveListenerOptions);
329324
});
@@ -372,18 +367,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
372367
return !!this._tooltipInstance && this._tooltipInstance.isVisible();
373368
}
374369

375-
/**
376-
* Handles the keydown events on the host element.
377-
* Needs to be an arrow function so that we can use it in addEventListener.
378-
*/
379-
private _handleKeydown = (event: KeyboardEvent) => {
380-
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
381-
event.preventDefault();
382-
event.stopPropagation();
383-
this._ngZone.run(() => this.hide(0));
384-
}
385-
}
386-
387370
/** Create the overlay config and position strategy */
388371
private _createOverlay(): OverlayRef {
389372
if (this._overlayRef) {
@@ -411,7 +394,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
411394
}
412395
});
413396

414-
this._overlayRef = this._overlay.create({
397+
const overlayRef = this._overlayRef = this._overlay.create({
415398
direction: this._dir,
416399
positionStrategy: strategy,
417400
panelClass: TOOLTIP_PANEL_CLASS,
@@ -420,11 +403,21 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
420403

421404
this._updatePosition();
422405

423-
this._overlayRef.detachments()
406+
overlayRef.keydownEvents()
407+
.pipe(takeUntil(this._destroyed))
408+
.subscribe(event => {
409+
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
410+
event.preventDefault();
411+
event.stopPropagation();
412+
this._ngZone.run(() => this.hide(0));
413+
}
414+
});
415+
416+
overlayRef.detachments()
424417
.pipe(takeUntil(this._destroyed))
425418
.subscribe(() => this._detach());
426419

427-
return this._overlayRef;
420+
return overlayRef;
428421
}
429422

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

0 commit comments

Comments
 (0)