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);
},
]);
}