diff --git a/src/cdk/overlay/overlay.md b/src/cdk/overlay/overlay.md
index e78674e181d..58c47f1dedf 100644
--- a/src/cdk/overlay/overlay.md
+++ b/src/cdk/overlay/overlay.md
@@ -43,7 +43,10 @@ overlay relative to another element on the page. These features include the abil
overlay become scrollable once its content reaches the viewport edge, being able to configure a
margin between the overlay and the viewport edge, having an overlay be pushed into the viewport if
it doesn't fit into any of its preferred positions, as well as configuring whether the overlay's
-size can grow while the overlay is open.
+size can grow while the overlay is open. The flexible position strategy also allows for the
+`transform-origin` of an element, inside the overlay, to be set based on the current position using
+the `withTransformOriginOn`. This is useful when animating an overlay in and having the animation
+originate from the point at which it connects with the origin.
A custom position strategy can be created by implementing the `PositionStrategy` interface.
Each `PositionStrategy` defines an `apply` method that is called whenever the overlay's position
diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts
index b91aaf50476..9873a1acb4d 100644
--- a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts
+++ b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts
@@ -507,6 +507,114 @@ describe('FlexibleConnectedPositionStrategy', () => {
});
+ describe('with transform origin', () => {
+ it('should set the proper transform-origin when aligning to start/bottom', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'start',
+ originY: 'bottom',
+ overlayX: 'start',
+ overlayY: 'top'
+ }]);
+
+ attachOverlay({positionStrategy});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('left top');
+ });
+
+ it('should set the proper transform-origin when aligning to end/bottom', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'end',
+ originY: 'bottom',
+ overlayX: 'end',
+ overlayY: 'top'
+ }]);
+
+ attachOverlay({positionStrategy});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('right top');
+ });
+
+ it('should set the proper transform-origin when centering vertically', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'start',
+ originY: 'center',
+ overlayX: 'start',
+ overlayY: 'center'
+ }]);
+
+ attachOverlay({positionStrategy});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('left center');
+ });
+
+ it('should set the proper transform-origin when centering horizontally', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'center',
+ originY: 'top',
+ overlayX: 'center',
+ overlayY: 'top'
+ }]);
+
+ attachOverlay({positionStrategy});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('center top');
+ });
+
+ it('should set the proper transform-origin when aligning to start/top', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'start',
+ originY: 'top',
+ overlayX: 'start',
+ overlayY: 'bottom'
+ }]);
+
+ attachOverlay({positionStrategy});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('left bottom');
+ });
+
+ it('should set the proper transform-origin when aligning to start/bottom in rtl', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'start',
+ originY: 'bottom',
+ overlayX: 'start',
+ overlayY: 'top'
+ }]);
+
+ attachOverlay({positionStrategy, direction: 'rtl'});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('right top');
+ });
+
+ it('should set the proper transform-origin when aligning to end/bottom in rtl', () => {
+ positionStrategy.withTransformOriginOn('.transform-origin').withPositions([{
+ originX: 'end',
+ originY: 'bottom',
+ overlayX: 'end',
+ overlayY: 'top'
+ }]);
+
+ attachOverlay({positionStrategy, direction: 'rtl'});
+
+ const target = overlayRef.overlayElement.querySelector('.transform-origin')! as HTMLElement;
+
+ expect(target.style.transformOrigin).toContain('left top');
+ });
+
+ });
+
it('should account for the `offsetX` pushing the overlay out of the screen', () => {
// Position the element so it would have enough space to fit.
originElement.style.top = '200px';
@@ -1676,7 +1784,11 @@ function createOverflowContainerElement() {
@Component({
- template: `
`
+ template: `
+
+ `
})
class TestOverlay { }
diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts
index a793cb568f2..bf507a58699 100644
--- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts
+++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts
@@ -24,8 +24,6 @@ import {Platform} from '@angular/cdk/platform';
// TODO: refactor clipping detection into a separate thing (part of scrolling module)
-// TODO: attribute selector to specify the transform-origin inside the overlay content
-// TODO: flexible position + centering doesn't work on IE11 (works on Edge).
// TODO: doesn't handle both flexible width and height when it has to scroll along both axis.
/**
@@ -108,6 +106,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
/** Default offset for the overlay along the y axis. */
private _offsetY = 0;
+ /** Selector to be used when finding the elements on which to set the transform origin. */
+ private _transformOriginSelector: string;
+
/** Observable sequence of position changes. */
positionChanges: Observable =
this._positionChanges.asObservable();
@@ -392,6 +393,19 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
return this;
}
+ /**
+ * Configures that the position strategy should set a `transform-origin` on some elements
+ * inside the overlay, depending on the current position that is being applied. This is
+ * useful for the cases where the origin of an animation can change depending on the
+ * alignment of the overlay.
+ * @param selector CSS selector that will be used to find the target
+ * elements onto which to set the transform origin.
+ */
+ withTransformOriginOn(selector: string): this {
+ this._transformOriginSelector = selector;
+ return this;
+ }
+
/**
* Gets the (x, y) coordinate of a connection point on the origin based on a relative position.
*/
@@ -556,11 +570,11 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
/**
* Applies a computed position to the overlay and emits a position change.
- *
* @param position The position preference
* @param originPoint The point on the origin element where the overlay is connected.
*/
private _applyPosition(position: ConnectedPosition, originPoint: Point) {
+ this._setTransformOrigin(position);
this._setOverlayElementStyles(originPoint, position);
this._setBoundingBoxStyles(originPoint, position);
@@ -574,6 +588,30 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
this._isInitialRender = false;
}
+ /** Sets the transform origin based on the configured selector and the passed-in position. */
+ private _setTransformOrigin(position: ConnectedPosition) {
+ if (!this._transformOriginSelector) {
+ return;
+ }
+
+ const elements: NodeListOf =
+ this._boundingBox!.querySelectorAll(this._transformOriginSelector);
+ let xOrigin: 'left' | 'right' | 'center';
+ let yOrigin: 'top' | 'bottom' | 'center' = position.overlayY;
+
+ if (position.overlayX === 'center') {
+ xOrigin = 'center';
+ } else if (this._isRtl()) {
+ xOrigin = position.overlayX === 'start' ? 'right' : 'left';
+ } else {
+ xOrigin = position.overlayX === 'start' ? 'left' : 'right';
+ }
+
+ for (let i = 0; i < elements.length; i++) {
+ elements[i].style.transformOrigin = `${xOrigin} ${yOrigin}`;
+ }
+ }
+
/**
* Gets the position and size of the overlay's sizing container.
*
diff --git a/src/lib/core/style/_menu-common.scss b/src/lib/core/style/_menu-common.scss
index d1527faeb50..87c254c0dbb 100644
--- a/src/lib/core/style/_menu-common.scss
+++ b/src/lib/core/style/_menu-common.scss
@@ -57,43 +57,3 @@ $mat-menu-icon-margin: 16px !default;
}
}
}
-
-/**
- * This mixin adds the correct panel transform styles based
- * on the direction that the menu panel opens.
- */
-@mixin mat-menu-positions() {
- &.mat-menu-after.mat-menu-below {
- transform-origin: left top;
- }
-
- &.mat-menu-after.mat-menu-above {
- transform-origin: left bottom;
- }
-
- &.mat-menu-before.mat-menu-below {
- transform-origin: right top;
- }
-
- &.mat-menu-before.mat-menu-above {
- transform-origin: right bottom;
- }
-
- [dir='rtl'] & {
- &.mat-menu-after.mat-menu-below {
- transform-origin: right top;
- }
-
- &.mat-menu-after.mat-menu-above {
- transform-origin: right bottom;
- }
-
- &.mat-menu-before.mat-menu-below {
- transform-origin: left top;
- }
-
- &.mat-menu-before.mat-menu-above {
- transform-origin: left bottom;
- }
- }
-}
diff --git a/src/lib/datepicker/datepicker-content.scss b/src/lib/datepicker/datepicker-content.scss
index c18025b6727..cba88c32e08 100644
--- a/src/lib/datepicker/datepicker-content.scss
+++ b/src/lib/datepicker/datepicker-content.scss
@@ -29,7 +29,6 @@ $mat-datepicker-touch-max-height: 788px;
display: block;
border-radius: 2px;
- transform-origin: top center;
.mat-calendar {
width: $mat-datepicker-non-touch-calendar-width;
@@ -37,10 +36,6 @@ $mat-datepicker-touch-max-height: 788px;
}
}
-.mat-datepicker-content-above {
- transform-origin: bottom center;
-}
-
.mat-datepicker-content-touch {
@include mat-elevation(0);
diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index c4297e6090b..5e706159997 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -1476,52 +1476,6 @@ describe('MatDatepicker', () => {
}));
});
- describe('popup animations', () => {
- let fixture: ComponentFixture;
-
- beforeEach(fakeAsync(() => {
- TestBed.configureTestingModule({
- imports: [MatDatepickerModule, MatNativeDateModule, NoopAnimationsModule],
- declarations: [StandardDatepicker],
- }).compileComponents();
-
- fixture = TestBed.createComponent(StandardDatepicker);
- fixture.detectChanges();
- }));
-
- it('should not set the `mat-datepicker-content-above` class when opening downwards',
- fakeAsync(() => {
- fixture.componentInstance.datepicker.open();
- fixture.detectChanges();
- flush();
- fixture.detectChanges();
-
- const content =
- document.querySelector('.cdk-overlay-pane mat-datepicker-content')! as HTMLElement;
-
- expect(content.classList).not.toContain('mat-datepicker-content-above');
- }));
-
- it('should set the `mat-datepicker-content-above` class when opening upwards', fakeAsync(() => {
- const input = fixture.debugElement.nativeElement.querySelector('input');
-
- // Push the input to the bottom of the page to force the calendar to open upwards
- input.style.position = 'fixed';
- input.style.bottom = '0';
-
- fixture.componentInstance.datepicker.open();
- fixture.detectChanges();
- flush();
- fixture.detectChanges();
-
- const content =
- document.querySelector('.cdk-overlay-pane mat-datepicker-content')! as HTMLElement;
-
- expect(content.classList).toContain('mat-datepicker-content-above');
- }));
-
- });
-
describe('datepicker with custom header', () => {
let fixture: ComponentFixture;
let testComponent: DatepickerWithCustomHeader;
diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts
index 818311312ff..9c7bcf02612 100644
--- a/src/lib/datepicker/datepicker.ts
+++ b/src/lib/datepicker/datepicker.ts
@@ -10,7 +10,6 @@ import {Directionality} from '@angular/cdk/bidi';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {ESCAPE, UP_ARROW} from '@angular/cdk/keycodes';
import {
- FlexibleConnectedPositionStrategy,
Overlay,
OverlayConfig,
OverlayRef,
@@ -23,7 +22,6 @@ import {take, filter} from 'rxjs/operators';
import {
AfterViewInit,
ChangeDetectionStrategy,
- ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
@@ -33,13 +31,12 @@ import {
InjectionToken,
Input,
NgZone,
- OnDestroy,
- OnInit,
Optional,
Output,
ViewChild,
ViewContainerRef,
ViewEncapsulation,
+ OnDestroy,
} from '@angular/core';
import {CanColor, DateAdapter, mixinColor, ThemePalette} from '@angular/material/core';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
@@ -88,7 +85,6 @@ export const _MatDatepickerContentMixinBase = mixinColor(MatDatepickerContentBas
'class': 'mat-datepicker-content',
'[@transformPanel]': '"enter"',
'[class.mat-datepicker-content-touch]': 'datepicker.touchUi',
- '[class.mat-datepicker-content-above]': '_isAbove',
},
animations: [
matDatepickerAnimations.transformPanel,
@@ -100,10 +96,7 @@ export const _MatDatepickerContentMixinBase = mixinColor(MatDatepickerContentBas
inputs: ['color'],
})
export class MatDatepickerContent extends _MatDatepickerContentMixinBase
- implements AfterViewInit, CanColor, OnInit, OnDestroy {
-
- /** Subscription to changes in the overlay's position. */
- private _positionChange: Subscription|null;
+ implements AfterViewInit, CanColor {
/** Reference to the internal calendar component. */
@ViewChild(MatCalendar) _calendar: MatCalendar;
@@ -114,43 +107,13 @@ export class MatDatepickerContent extends _MatDatepickerContentMixinBase
/** Whether the datepicker is above or below the input. */
_isAbove: boolean;
- constructor(
- elementRef: ElementRef,
- private _changeDetectorRef: ChangeDetectorRef,
- private _ngZone: NgZone) {
+ constructor(elementRef: ElementRef) {
super(elementRef);
}
- ngOnInit() {
- if (!this.datepicker._popupRef || this._positionChange) {
- return;
- }
-
- const positionStrategy =
- this.datepicker._popupRef.getConfig().positionStrategy! as FlexibleConnectedPositionStrategy;
-
- this._positionChange = positionStrategy.positionChanges.subscribe(change => {
- const isAbove = change.connectionPair.overlayY === 'bottom';
-
- if (isAbove !== this._isAbove) {
- this._ngZone.run(() => {
- this._isAbove = isAbove;
- this._changeDetectorRef.markForCheck();
- });
- }
- });
- }
-
ngAfterViewInit() {
this._calendar.focusActiveCell();
}
-
- ngOnDestroy() {
- if (this._positionChange) {
- this._positionChange.unsubscribe();
- this._positionChange = null;
- }
- }
}
@@ -478,6 +441,7 @@ export class MatDatepicker implements OnDestroy, CanColor {
private _createPopupPositionStrategy(): PositionStrategy {
return this._overlay.position()
.flexibleConnectedTo(this._datepickerInput.getPopupConnectionElementRef())
+ .withTransformOriginOn('.mat-datepicker-content')
.withFlexibleDimensions(false)
.withViewportMargin(8)
.withPush(false)
diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts
index 6ce15cc2ced..e1a8a63b2ce 100644
--- a/src/lib/menu/menu-directive.ts
+++ b/src/lib/menu/menu-directive.ts
@@ -23,7 +23,6 @@ import {
Input,
NgZone,
OnDestroy,
- OnInit,
Output,
TemplateRef,
QueryList,
@@ -97,7 +96,7 @@ const MAT_MENU_BASE_ELEVATION = 2;
{provide: MAT_MENU_PANEL, useExisting: MatMenu}
]
})
-export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestroy {
+export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy {
private _keyManager: FocusKeyManager;
private _xPosition: MenuPositionX = this._defaultOptions.xPosition;
private _yPosition: MenuPositionY = this._defaultOptions.yPosition;
@@ -141,7 +140,6 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel(this._items).withWrap().withTypeAhead();
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close.emit('tab'));
@@ -306,17 +298,6 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel {
direction?: Direction;
focusFirstItem: (origin?: FocusOrigin) => void;
resetActiveItem: () => void;
- setPositionClasses: (x: MenuPositionX, y: MenuPositionY) => void;
+ setPositionClasses?: (x: MenuPositionX, y: MenuPositionY) => void;
setElevation?(depth: number): void;
lazyContent?: MatMenuContent;
backdropClass?: string;
diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts
index bd1389d3fbd..885c828834e 100644
--- a/src/lib/menu/menu-trigger.ts
+++ b/src/lib/menu/menu-trigger.ts
@@ -353,12 +353,14 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
* correct, even if a fallback position is used for the overlay.
*/
private _subscribeToPositions(position: FlexibleConnectedPositionStrategy): void {
- position.positionChanges.subscribe(change => {
- const posX: MenuPositionX = change.connectionPair.overlayX === 'start' ? 'after' : 'before';
- const posY: MenuPositionY = change.connectionPair.overlayY === 'top' ? 'below' : 'above';
+ if (this.menu.setPositionClasses) {
+ position.positionChanges.subscribe(change => {
+ const posX: MenuPositionX = change.connectionPair.overlayX === 'start' ? 'after' : 'before';
+ const posY: MenuPositionY = change.connectionPair.overlayY === 'top' ? 'below' : 'above';
- this.menu.setPositionClasses(posX, posY);
- });
+ this.menu.setPositionClasses!(posX, posY);
+ });
+ }
}
/**
@@ -390,6 +392,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
return this._overlay.position()
.flexibleConnectedTo(this._element)
+ .withTransformOriginOn('.mat-menu-panel')
.withPositions([
{originX, originY, overlayX, overlayY, offsetY},
{originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY},
diff --git a/src/lib/menu/menu.scss b/src/lib/menu/menu.scss
index 043ca34909a..4bead698e7a 100644
--- a/src/lib/menu/menu.scss
+++ b/src/lib/menu/menu.scss
@@ -11,7 +11,6 @@ $mat-menu-submenu-indicator-size: 10px !default;
.mat-menu-panel {
@include mat-menu-base(2);
- @include mat-menu-positions();
max-height: calc(100vh - #{$mat-menu-item-height});
border-radius: $mat-menu-border-radius;
outline: 0;
diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts
index ebd0c103993..ca4be231582 100644
--- a/src/lib/menu/menu.spec.ts
+++ b/src/lib/menu/menu.spec.ts
@@ -463,68 +463,6 @@ describe('MatMenu', () => {
}));
});
- describe('positions', () => {
- let fixture: ComponentFixture;
- let panel: HTMLElement;
-
- beforeEach(() => {
- fixture = createComponent(PositionedMenu);
- fixture.detectChanges();
-
- const trigger = fixture.componentInstance.triggerEl.nativeElement;
-
- // Push trigger to the bottom edge of viewport,so it has space to open "above"
- trigger.style.position = 'fixed';
- trigger.style.top = '600px';
-
- // Push trigger to the right, so it has space to open "before"
- trigger.style.left = '100px';
-
- fixture.componentInstance.trigger.openMenu();
- fixture.detectChanges();
- panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement;
- });
-
- it('should append mat-menu-before if the x position is changed', () => {
- expect(panel.classList).toContain('mat-menu-before');
- expect(panel.classList).not.toContain('mat-menu-after');
-
- fixture.componentInstance.xPosition = 'after';
- fixture.detectChanges();
-
- expect(panel.classList).toContain('mat-menu-after');
- expect(panel.classList).not.toContain('mat-menu-before');
- });
-
- it('should append mat-menu-above if the y position is changed', () => {
- expect(panel.classList).toContain('mat-menu-above');
- expect(panel.classList).not.toContain('mat-menu-below');
-
- fixture.componentInstance.yPosition = 'below';
- fixture.detectChanges();
-
- expect(panel.classList).toContain('mat-menu-below');
- expect(panel.classList).not.toContain('mat-menu-above');
- });
-
- it('should default to the "below" and "after" positions', () => {
- overlayContainer.ngOnDestroy();
- fixture.destroy();
- TestBed.resetTestingModule();
-
- const newFixture = createComponent(SimpleMenu, [], [FakeIcon]);
-
- newFixture.detectChanges();
- newFixture.componentInstance.trigger.openMenu();
- newFixture.detectChanges();
- panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement;
-
- expect(panel.classList).toContain('mat-menu-below');
- expect(panel.classList).toContain('mat-menu-after');
- });
-
- });
-
describe('fallback positions', () => {
it('should fall back to "before" mode if "after" mode would not fit on screen', () => {
@@ -730,14 +668,6 @@ describe('MatMenu', () => {
.toBe(Math.floor(subject.triggerRect.top),
`Expected menu to open in "above" position if "below" position wouldn't fit.`);
});
-
- it('repositions the origin to be below, so the menu opens from the trigger', () => {
- subject.openMenu();
- subject.fixture.detectChanges();
-
- expect(subject.menuPanel!.classList).toContain('mat-menu-below');
- expect(subject.menuPanel!.classList).not.toContain('mat-menu-above');
- });
});
});
diff --git a/src/lib/tooltip/tooltip.html b/src/lib/tooltip/tooltip.html
index 22268ef9a94..b21865b8b1b 100644
--- a/src/lib/tooltip/tooltip.html
+++ b/src/lib/tooltip/tooltip.html
@@ -1,7 +1,6 @@
{{message}}
diff --git a/src/lib/tooltip/tooltip.spec.ts b/src/lib/tooltip/tooltip.spec.ts
index 21ad4e3887a..4a8044293f9 100644
--- a/src/lib/tooltip/tooltip.spec.ts
+++ b/src/lib/tooltip/tooltip.spec.ts
@@ -482,54 +482,6 @@ describe('MatTooltip', () => {
expect(tooltipDirective._getOverlayPosition().main).toEqual(rightOverlayPosition);
});
- it('should have consistent left transform origin in ltr', () => {
- tooltipDirective.position = 'right';
- tooltipDirective.show();
- fixture.detectChanges();
- expect(tooltipDirective._tooltipInstance!._transformOrigin).toBe('left');
-
- tooltipDirective.position = 'after';
- tooltipDirective.show();
- fixture.detectChanges();
- expect(tooltipDirective._tooltipInstance!._transformOrigin).toBe('left');
- });
-
- it('should have consistent left transform origin in rtl', () => {
- dir.value = 'rtl';
- tooltipDirective.position = 'before';
- tooltipDirective.show();
- fixture.detectChanges();
- expect(tooltipDirective._tooltipInstance!._transformOrigin).toBe('left');
- });
-
- it('should have consistent right transform origin in ltr', () => {
- // Move the button away from the edge of the screen so
- // we don't get into the fallback positions.
- fixture.componentInstance.button.nativeElement.style.margin = '300px';
-
- tooltipDirective.position = 'left';
- tooltipDirective.show();
- fixture.detectChanges();
- expect(tooltipDirective._tooltipInstance!._transformOrigin).toBe('right');
-
- tooltipDirective.position = 'before';
- tooltipDirective.show();
- fixture.detectChanges();
- expect(tooltipDirective._tooltipInstance!._transformOrigin).toBe('right');
- });
-
- it('should have consistent right transform origin in rtl', () => {
- // Move the button away from the edge of the screen so
- // we don't get into the fallback positions.
- fixture.componentInstance.button.nativeElement.style.margin = '300px';
-
- dir.value = 'rtl';
- tooltipDirective.position = 'after';
- tooltipDirective.show();
- fixture.detectChanges();
- expect(tooltipDirective._tooltipInstance!._transformOrigin).toBe('right');
- });
-
it('should throw when trying to assign an invalid position', () => {
expect(() => {
fixture.componentInstance.position = 'everywhere';
diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts
index cf726f821b6..4b9d6442837 100644
--- a/src/lib/tooltip/tooltip.ts
+++ b/src/lib/tooltip/tooltip.ts
@@ -7,12 +7,11 @@
*/
import {AnimationEvent} from '@angular/animations';
import {AriaDescriber, FocusMonitor} from '@angular/cdk/a11y';
-import {Direction, Directionality} from '@angular/cdk/bidi';
+import {Directionality} from '@angular/cdk/bidi';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {ESCAPE} from '@angular/cdk/keycodes';
import {BreakpointObserver, Breakpoints, BreakpointState} from '@angular/cdk/layout';
import {
- ConnectionPositionPair,
FlexibleConnectedPositionStrategy,
HorizontalConnectionPos,
OriginConnectionPosition,
@@ -129,7 +128,7 @@ export class MatTooltip implements OnDestroy {
this._updatePosition();
if (this._tooltipInstance) {
- this._tooltipInstance!.show(value, 0);
+ this._tooltipInstance!.show(0);
}
this._overlayRef.updatePosition();
@@ -270,7 +269,7 @@ export class MatTooltip implements OnDestroy {
.subscribe(() => this._detach());
this._setTooltipClass(this._tooltipClass);
this._updateTooltipMessage();
- this._tooltipInstance!.show(this._position, delay);
+ this._tooltipInstance!.show(delay);
}
/** Hides the tooltip after the delay in ms, defaults to tooltip-delay-hide or 0ms if no input */
@@ -316,6 +315,7 @@ export class MatTooltip implements OnDestroy {
// Create connected position strategy that listens for scroll events to reposition.
const strategy = this._overlay.position()
.flexibleConnectedTo(this._elementRef)
+ .withTransformOriginOn('.mat-tooltip')
.withFlexibleDimensions(false)
.withViewportMargin(8)
.withPositions([
@@ -334,9 +334,6 @@ export class MatTooltip implements OnDestroy {
// After position changes occur and the overlay is clipped by
// a parent scrollable then close the tooltip.
this._ngZone.run(() => this.hide(0));
- } else {
- // Otherwise recalculate the origin based on the new position.
- this._tooltipInstance._setTransformOrigin(change.connectionPair, direction);
}
}
});
@@ -531,12 +528,6 @@ export class TooltipComponent {
/** Whether interactions on the page should close the tooltip */
private _closeOnInteraction: boolean = false;
- /** The transform origin used in the animation for showing and hiding the tooltip */
- _transformOrigin: 'top' | 'bottom' | 'left' | 'right' = 'bottom';
-
- /** Current position of the tooltip. */
- private _position: TooltipPosition;
-
/** Subject for notifying that the tooltip has been hidden from the view */
private readonly _onHide: Subject = new Subject();
@@ -549,10 +540,9 @@ export class TooltipComponent {
/**
* Shows the tooltip with an animation originating from the provided origin
- * @param position Position of the tooltip.
* @param delay Amount of milliseconds to the delay showing the tooltip.
*/
- show(position: TooltipPosition, delay: number): void {
+ show(delay: number): void {
// Cancel the delayed hide if it is scheduled
if (this._hideTimeoutId) {
clearTimeout(this._hideTimeoutId);
@@ -560,7 +550,6 @@ export class TooltipComponent {
// Body interactions should cancel the tooltip if there is a delay in showing.
this._closeOnInteraction = true;
- this._position = position;
this._showTimeoutId = setTimeout(() => {
this._visibility = 'visible';
@@ -599,22 +588,6 @@ export class TooltipComponent {
return this._visibility === 'visible';
}
- /** Sets the tooltip transform origin according to the position of the tooltip overlay. */
- _setTransformOrigin(overlayPosition: ConnectionPositionPair, direction: Direction) {
- const axis = (this._position === 'above' || this._position === 'below') ? 'Y' : 'X';
- const position = axis == 'X' ? overlayPosition.overlayX : overlayPosition.overlayY;
-
- if (position === 'top' || position === 'bottom') {
- this._transformOrigin = position;
- } else if (position === 'start') {
- this._transformOrigin = direction === 'ltr' ? 'left' : 'right';
- } else if (position === 'end') {
- this._transformOrigin = direction === 'ltr' ? 'right' : 'left';
- } else {
- throw getMatTooltipInvalidPositionError(this._position);
- }
- }
-
_animationStart() {
this._closeOnInteraction = false;
}