diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index 65f68bc434d3..fc9103663a26 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -98,7 +98,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { _preferredPositions: ConnectionPositionPair[] = []; /** The origin element against which the overlay will be positioned. */ - private _origin: FlexibleConnectedPositionStrategyOrigin; + _origin: FlexibleConnectedPositionStrategyOrigin; /** The overlay pane element. */ private _pane: HTMLElement; diff --git a/src/components-examples/material/tooltip/index.ts b/src/components-examples/material/tooltip/index.ts index e3d793ea17db..4ad892a840eb 100644 --- a/src/components-examples/material/tooltip/index.ts +++ b/src/components-examples/material/tooltip/index.ts @@ -16,6 +16,7 @@ import {TooltipMessageExample} from './tooltip-message/tooltip-message-example'; import {TooltipModifiedDefaultsExample} from './tooltip-modified-defaults/tooltip-modified-defaults-example'; import {TooltipOverviewExample} from './tooltip-overview/tooltip-overview-example'; import {TooltipPositionExample} from './tooltip-position/tooltip-position-example'; +import {TooltipPositionAtOriginExample} from './tooltip-position-at-origin/tooltip-position-at-origin-example'; import {TooltipHarnessExample} from './tooltip-harness/tooltip-harness-example'; export { @@ -29,6 +30,7 @@ export { TooltipModifiedDefaultsExample, TooltipOverviewExample, TooltipPositionExample, + TooltipPositionAtOriginExample, }; const EXAMPLES = [ @@ -42,6 +44,7 @@ const EXAMPLES = [ TooltipModifiedDefaultsExample, TooltipOverviewExample, TooltipPositionExample, + TooltipPositionAtOriginExample, ]; @NgModule({ diff --git a/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css new file mode 100644 index 000000000000..82d14164d994 --- /dev/null +++ b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css @@ -0,0 +1,8 @@ +button { + width: 500px; + height: 500px; +} + +.example-enabled-checkbox { + margin-left: 8px; +} diff --git a/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.html b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.html new file mode 100644 index 000000000000..e1aee7f8f4b7 --- /dev/null +++ b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.html @@ -0,0 +1,10 @@ + + + + Position at origin enabled + diff --git a/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.ts b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.ts new file mode 100644 index 000000000000..1bfb48e4fb1b --- /dev/null +++ b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; +import {FormControl} from '@angular/forms'; + +/** + * @title Basic tooltip + */ +@Component({ + selector: 'tooltip-position-at-origin-example', + templateUrl: 'tooltip-position-at-origin-example.html', + styleUrls: ['tooltip-position-at-origin-example.css'], +}) +export class TooltipPositionAtOriginExample { + enabled = new FormControl(false); +} diff --git a/src/dev-app/tooltip/tooltip-demo.html b/src/dev-app/tooltip/tooltip-demo.html index 0c7f2bede630..b848c7acd64f 100644 --- a/src/dev-app/tooltip/tooltip-demo.html +++ b/src/dev-app/tooltip/tooltip-demo.html @@ -24,3 +24,6 @@

Tooltip overview

Tooltip positioning

+ +

Tooltip with position at origin

+ diff --git a/src/material/legacy-tooltip/tooltip.spec.ts b/src/material/legacy-tooltip/tooltip.spec.ts index 72a816d402cc..e64798260154 100644 --- a/src/material/legacy-tooltip/tooltip.spec.ts +++ b/src/material/legacy-tooltip/tooltip.spec.ts @@ -11,6 +11,7 @@ import { dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent, + dispatchTouchEvent, patchElementFocus, } from '../../cdk/testing/private'; import { @@ -232,6 +233,63 @@ describe('MatTooltip', () => { expect(tooltipDirective._getOverlayPosition().fallback.overlayX).toBe('end'); })); + it('should position center-bottom by default', fakeAsync(() => { + TestBed.resetTestingModule() + .configureTestingModule({ + imports: [MatLegacyTooltipModule, OverlayModule], + declarations: [WideTooltipDemo] + }) + .compileComponents(); + + const wideFixture = TestBed.createComponent(WideTooltipDemo); + wideFixture.detectChanges(); + tooltipDirective = wideFixture.debugElement + .query(By.css('button'))! + .injector.get(MatLegacyTooltip); + const button: HTMLButtonElement = wideFixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); + + dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 100, triggerRect.top + 100); + wideFixture.detectChanges(); + tick(); + expect(tooltipDirective._isTooltipVisible()).toBe(true); + + expect(tooltipDirective._overlayRef!.overlayElement.offsetLeft).toBeGreaterThan(triggerRect.left + 200); + expect(tooltipDirective._overlayRef!.overlayElement.offsetLeft).toBeLessThan(triggerRect.left + 300); + expect(tooltipDirective._overlayRef!.overlayElement.offsetTop).toBe(triggerRect.bottom); + })); + + it('should be able to override the default positionAtOrigin', fakeAsync(() => { + TestBed.resetTestingModule() + .configureTestingModule({ + imports: [MatLegacyTooltipModule, OverlayModule], + declarations: [WideTooltipDemo], + providers: [ + { + provide: MAT_TOOLTIP_DEFAULT_OPTIONS, + useValue: {positionAtOrigin: true}, + }, + ], + }) + .compileComponents(); + + const wideFixture = TestBed.createComponent(WideTooltipDemo); + wideFixture.detectChanges(); + tooltipDirective = wideFixture.debugElement + .query(By.css('button'))! + .injector.get(MatLegacyTooltip); + const button: HTMLButtonElement = wideFixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); + + dispatchMouseEvent(button, 'mouseenter', triggerRect.left + 50, triggerRect.bottom - 10); + wideFixture.detectChanges(); + tick(); + expect(tooltipDirective._isTooltipVisible()).toBe(true); + + expect(tooltipDirective._overlayRef!.overlayElement.offsetLeft).toBe(triggerRect.left + 28); + expect(tooltipDirective._overlayRef!.overlayElement.offsetTop).toBe(triggerRect.bottom - 10); + })); + it('should be able to disable tooltip interactivity', fakeAsync(() => { TestBed.resetTestingModule() .configureTestingModule({ @@ -1169,7 +1227,10 @@ describe('MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(250); // Halfway through the delay. @@ -1188,7 +1249,10 @@ describe('MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(500); // Finish the delay. fixture.detectChanges(); @@ -1201,7 +1265,10 @@ describe('MatTooltip', () => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - const event = dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + const event = dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); expect(event.defaultPrevented).toBe(false); @@ -1212,7 +1279,10 @@ describe('MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1236,7 +1306,10 @@ describe('MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1401,8 +1474,9 @@ describe('MatTooltip', () => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); - dispatchFakeEvent(button, 'mouseenter'); + dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 10, triggerRect.top + 10); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1410,7 +1484,6 @@ describe('MatTooltip', () => { assertTooltipInstance(fixture.componentInstance.tooltip, true); // Simulate the pointer over the trigger. - const triggerRect = button.getBoundingClientRect(); const wheelEvent = createFakeEvent('wheel'); Object.defineProperties(wheelEvent, { clientX: {get: () => triggerRect.left + 1}, @@ -1556,6 +1629,17 @@ class TooltipDemoWithoutPositionBinding { @ViewChild('button') button: ElementRef; } +@Component({ + selector: 'app', + styles: [`button { width: 500px; height: 500px; }`], + template: ``, +}) +class WideTooltipDemo { + message = 'Test'; + @ViewChild(MatLegacyTooltip) tooltip: MatLegacyTooltip; + @ViewChild('button') button: ElementRef; +} + /** Asserts whether a tooltip directive has a tooltip instance. */ function assertTooltipInstance(tooltip: MatLegacyTooltip, shouldExist: boolean): void { // Note that we have to cast this to a boolean, because Jasmine will go into an infinite loop diff --git a/src/material/tooltip/tooltip.md b/src/material/tooltip/tooltip.md index 69e2aaf44d29..079bd174d1b5 100644 --- a/src/material/tooltip/tooltip.md +++ b/src/material/tooltip/tooltip.md @@ -27,6 +27,12 @@ CSS class that can be used for style (e.g. to add an arrow). The possible classe +To display the tooltip relative to the mouse or touch that triggered it, use the +`matTooltipPositionAtOrigin` input. +With this setting turned on, the tooltip will display relative to the origin of the trigger rather +than the host element. In cases where the tooltip is not triggered by a touch event or mouse click, +it will display the same as if this setting was turned off. + ### Showing and hiding By default, the tooltip will be immediately shown when the user's mouse hovers over the tooltip's diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts index 369779bad0e3..c44feb0266ce 100644 --- a/src/material/tooltip/tooltip.spec.ts +++ b/src/material/tooltip/tooltip.spec.ts @@ -11,6 +11,7 @@ import { dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent, + dispatchTouchEvent, patchElementFocus, } from '../../cdk/testing/private'; import { @@ -234,6 +235,62 @@ describe('MDC-based MatTooltip', () => { expect(tooltipDirective._getOverlayPosition().fallback.overlayX).toBe('end'); })); + it('should position on the bottom-left by default', fakeAsync(() => { + TestBed.resetTestingModule() + .configureTestingModule({ + imports: [MatTooltipModule, OverlayModule], + declarations: [WideTooltipDemo] + }) + .compileComponents(); + + const wideFixture = TestBed.createComponent(WideTooltipDemo); + wideFixture.detectChanges(); + tooltipDirective = wideFixture.debugElement + .query(By.css('button'))! + .injector.get(MatTooltip); + const button: HTMLButtonElement = wideFixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); + + dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 100, triggerRect.top + 100); + wideFixture.detectChanges(); + tick(); + expect(tooltipDirective._isTooltipVisible()).toBe(true); + + expect(tooltipDirective._overlayRef!.overlayElement.offsetLeft).toBeLessThan(triggerRect.right - 250); + expect(tooltipDirective._overlayRef!.overlayElement.offsetTop).toBeGreaterThanOrEqual(triggerRect.bottom); + })); + + it('should be able to override the default positionAtOrigin', fakeAsync(() => { + TestBed.resetTestingModule() + .configureTestingModule({ + imports: [MatTooltipModule, OverlayModule], + declarations: [WideTooltipDemo], + providers: [ + { + provide: MAT_TOOLTIP_DEFAULT_OPTIONS, + useValue: {positionAtOrigin: true}, + }, + ], + }) + .compileComponents(); + + const wideFixture = TestBed.createComponent(WideTooltipDemo); + wideFixture.detectChanges(); + tooltipDirective = wideFixture.debugElement + .query(By.css('button'))! + .injector.get(MatTooltip); + const button: HTMLButtonElement = wideFixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); + + dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 100, triggerRect.top + 100); + wideFixture.detectChanges(); + tick(); + expect(tooltipDirective._isTooltipVisible()).toBe(true); + + expect(tooltipDirective._overlayRef!.overlayElement.offsetLeft).toBe(triggerRect.right - 100 - 20); + expect(tooltipDirective._overlayRef!.overlayElement.offsetTop).toBe(triggerRect.top + 100); + })); + it('should be able to disable tooltip interactivity', fakeAsync(() => { TestBed.resetTestingModule() .configureTestingModule({ @@ -1201,7 +1258,10 @@ describe('MDC-based MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(250); // Halfway through the delay. @@ -1220,7 +1280,10 @@ describe('MDC-based MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(500); // Finish the delay. fixture.detectChanges(); @@ -1233,7 +1296,10 @@ describe('MDC-based MatTooltip', () => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - const event = dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + const event = dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); expect(event.defaultPrevented).toBe(false); @@ -1244,7 +1310,10 @@ describe('MDC-based MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1268,7 +1337,10 @@ describe('MDC-based MatTooltip', () => { fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); - dispatchFakeEvent(button, 'touchstart'); + const triggerRect = button.getBoundingClientRect(); + const offsetX = triggerRect.right - 10; + const offsetY = triggerRect.top + 10; + dispatchTouchEvent(button, 'touchstart', offsetX, offsetY, offsetX, offsetY); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1400,8 +1472,9 @@ describe('MDC-based MatTooltip', () => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); - dispatchFakeEvent(button, 'mouseenter'); + dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 10, triggerRect.top + 10); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1433,8 +1506,9 @@ describe('MDC-based MatTooltip', () => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); + const triggerRect = button.getBoundingClientRect(); - dispatchFakeEvent(button, 'mouseenter'); + dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 10, triggerRect.top + 10); fixture.detectChanges(); tick(500); // Finish the open delay. fixture.detectChanges(); @@ -1442,7 +1516,6 @@ describe('MDC-based MatTooltip', () => { assertTooltipInstance(fixture.componentInstance.tooltip, true); // Simulate the pointer over the trigger. - const triggerRect = button.getBoundingClientRect(); const wheelEvent = createFakeEvent('wheel'); Object.defineProperties(wheelEvent, { clientX: {get: () => triggerRect.left + 1}, @@ -1588,6 +1661,17 @@ class TooltipDemoWithoutPositionBinding { @ViewChild('button') button: ElementRef; } +@Component({ + selector: 'app', + styles: [`button { width: 500px; height: 500px; }`], + template: ``, +}) +class WideTooltipDemo { + message = 'Test'; + @ViewChild(MatTooltip) tooltip: MatTooltip; + @ViewChild('button') button: ElementRef; +} + /** Asserts whether a tooltip directive has a tooltip instance. */ function assertTooltipInstance(tooltip: MatTooltip, shouldExist: boolean): void { // Note that we have to cast this to a boolean, because Jasmine will go into an infinite loop diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 4dbb65e9d9f5..c0dfeda735c0 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -128,6 +128,12 @@ export interface MatTooltipDefaultOptions { /** Default position for tooltips. */ position?: TooltipPosition; + /** + * Default value for whether tooltips should be positioned near the click or touch origin + * instead of outside the element bounding box. + */ + positionAtOrigin?: boolean; + /** Disables the ability for the user to interact with the tooltip element. */ disableTooltipInteractivity?: boolean; } @@ -159,6 +165,7 @@ export abstract class _MatTooltipBase private _portal: ComponentPortal; private _position: TooltipPosition = 'below'; + private _positionAtOrigin: boolean = false; private _disabled: boolean = false; private _tooltipClass: string | string[] | Set | {[key: string]: any}; private _scrollStrategy: () => ScrollStrategy; @@ -187,6 +194,16 @@ export abstract class _MatTooltipBase } } + @Input('matTooltipPositionAtOrigin') + get positionAtOrigin(): boolean { + return this._positionAtOrigin; + } + set positionAtOrigin(value: BooleanInput) { + this._positionAtOrigin = coerceBooleanProperty(value); + this._detach(); + this._overlayRef = null; + } + /** Disables the display of the tooltip. */ @Input('matTooltipDisabled') get disabled(): boolean { @@ -329,6 +346,10 @@ export abstract class _MatTooltipBase this.position = _defaultOptions.position; } + if (_defaultOptions.positionAtOrigin) { + this.positionAtOrigin = _defaultOptions.positionAtOrigin; + } + if (_defaultOptions.touchGestures) { this.touchGestures = _defaultOptions.touchGestures; } @@ -386,7 +407,7 @@ export abstract class _MatTooltipBase } /** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */ - show(delay: number = this.showDelay): void { + show(delay: number = this.showDelay, origin?: { x: number, y: number }): void { if ( this.disabled || !this.message || @@ -397,7 +418,7 @@ export abstract class _MatTooltipBase return; } - const overlayRef = this._createOverlay(); + const overlayRef = this._createOverlay(origin); this._detach(); this._portal = this._portal || new ComponentPortal(this._tooltipComponent, this._viewContainerRef); @@ -421,8 +442,8 @@ export abstract class _MatTooltipBase } /** Shows/hides the tooltip */ - toggle(): void { - this._isTooltipVisible() ? this.hide() : this.show(); + toggle(origin?: { x: number; y: number; }): void { + this._isTooltipVisible() ? this.hide() : this.show(undefined, origin); } /** Returns true if the tooltip is currently visible to the user */ @@ -431,9 +452,16 @@ export abstract class _MatTooltipBase } /** Create the overlay config and position strategy */ - private _createOverlay(): OverlayRef { + private _createOverlay(origin?: { x: number; y: number; }): OverlayRef { if (this._overlayRef) { - return this._overlayRef; + const existingStrategy = + this._overlayRef.getConfig().positionStrategy as FlexibleConnectedPositionStrategy; + + if ((!this.positionAtOrigin || !origin) && existingStrategy._origin instanceof ElementRef) { + return this._overlayRef; + } + + this._detach(); } const scrollableAncestors = this._scrollDispatcher.getAncestorScrollContainers( @@ -443,7 +471,7 @@ export abstract class _MatTooltipBase // Create connected position strategy that listens for scroll events to reposition. const strategy = this._overlay .position() - .flexibleConnectedTo(this._elementRef) + .flexibleConnectedTo(this.positionAtOrigin ? (origin || this._elementRef) : this._elementRef) .withTransformOriginOn(`.${this._cssClassPrefix}-tooltip`) .withFlexibleDimensions(false) .withViewportMargin(this._viewportMargin) @@ -686,9 +714,9 @@ export abstract class _MatTooltipBase if (this._platformSupportsMouseEvents()) { this._passiveListeners.push([ 'mouseenter', - () => { + event => { this._setupPointerExitEventsIfNeeded(); - this.show(); + this.show(undefined, { x: (event as MouseEvent).x, y: (event as MouseEvent).y }); }, ]); } else if (this.touchGestures !== 'off') { @@ -696,12 +724,14 @@ export abstract class _MatTooltipBase this._passiveListeners.push([ 'touchstart', - () => { + event => { + const touch = (event as TouchEvent).targetTouches[0]; + const origin = touch ? { x: touch.clientX, y: touch.clientY } : undefined; // Note that it's important that we don't `preventDefault` here, // because it can prevent click events from firing on the element. this._setupPointerExitEventsIfNeeded(); clearTimeout(this._touchstartTimeout); - this._touchstartTimeout = setTimeout(() => this.show(), LONGPRESS_DELAY); + this._touchstartTimeout = setTimeout(() => this.show(undefined, origin), LONGPRESS_DELAY); }, ]); }