fix(tooltip) add RTL support (#1830)

* feat(tooltip): support rtl

* remove fdescribe

* more changes for before, after

* fix test

* rebase

* minor change
This commit is contained in:
Andrew Seguin
2016-12-05 13:51:19 -08:00
committed by mmalerba
parent 5cf7d175d6
commit 54bf6ceb1a
4 changed files with 135 additions and 24 deletions
+2
View File
@@ -15,6 +15,8 @@
<md-radio-group [(ngModel)]="position">
<md-radio-button value="below">Below</md-radio-button>
<md-radio-button value="above">Above</md-radio-button>
<md-radio-button value="left">Left</md-radio-button>
<md-radio-button value="right">Right</md-radio-button>
<md-radio-button value="before">Before</md-radio-button>
<md-radio-button value="after">After</md-radio-button>
</md-radio-group>
+8 -4
View File
@@ -1,24 +1,28 @@
# MdTooltip
Tooltip allows the user to specify text to be displayed when the mouse hover over an element.
Tooltip allows the user to specify text to be displayed when the mouse hovers over an element.
The positions `before` and `after` should be used instead of `left` and `right` respectively when the tooltip positioning should change depending on the HTML dir global attribute for left-to-right or right-to-left layout.
### Examples
A button with a tooltip
```html
<button md-tooltip="some message" tooltip-position="below">Button</button>
<button md-tooltip="some message" tooltip-position="bottom">Button</button>
```
## `[md-tooltip]`
### Properties
| Name | Type | Description |
| --- | --- | --- |
| `md-tooltip` | `string` | The message to be displayed. |
| `tooltip-position` | `"above"|"below"|"before"|"after"` | The position of the tooltip. |
| `tooltip-position` | `"before"|"after"|"above"|"below"|"left"|"right"` | The position of the tooltip. |
### Methods
| Name | Description |
| --- | --- | --- |
| --- | --- |
| `show` | Displays the tooltip. |
| `hide` | Removes the tooltip. |
| `toggle` | Displays or hides the tooltip. |
+75 -1
View File
@@ -10,12 +10,13 @@ import {Component, DebugElement, AnimationTransitionEvent} from '@angular/core';
import {By} from '@angular/platform-browser';
import {TooltipPosition, MdTooltip, MdTooltipModule} from './tooltip';
import {OverlayContainer} from '../core';
import {Dir, LayoutDirection} from '../core/rtl/dir';
const initialTooltipMessage = 'initial tooltip message';
describe('MdTooltip', () => {
let overlayContainerElement: HTMLElement;
let dir: {value: LayoutDirection};
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -25,6 +26,9 @@ describe('MdTooltip', () => {
{provide: OverlayContainer, useFactory: () => {
overlayContainerElement = document.createElement('div');
return {getContainerElement: () => overlayContainerElement};
}},
{provide: Dir, useFactory: () => {
return dir = { value: 'ltr' };
}}
]
});
@@ -155,6 +159,76 @@ describe('MdTooltip', () => {
phaseName: '',
}));
}));
it('should consistently position before and after overlay origin in ltr and rtl dir', () => {
tooltipDirective.position = 'left';
const leftOrigin = tooltipDirective._getOrigin();
tooltipDirective.position = 'right';
const rightOrigin = tooltipDirective._getOrigin();
// Test expectations in LTR
tooltipDirective.position = 'before';
expect(tooltipDirective._getOrigin()).toEqual(leftOrigin);
tooltipDirective.position = 'after';
expect(tooltipDirective._getOrigin()).toEqual(rightOrigin);
// Test expectations in LTR
dir.value = 'rtl';
tooltipDirective.position = 'before';
expect(tooltipDirective._getOrigin()).toEqual(rightOrigin);
tooltipDirective.position = 'after';
expect(tooltipDirective._getOrigin()).toEqual(leftOrigin);
});
it('should consistently position before and after overlay position in ltr and rtl dir', () => {
tooltipDirective.position = 'left';
const leftOverlayPosition = tooltipDirective._getOverlayPosition();
tooltipDirective.position = 'right';
const rightOverlayPosition = tooltipDirective._getOverlayPosition();
// Test expectations in LTR
tooltipDirective.position = 'before';
expect(tooltipDirective._getOverlayPosition()).toEqual(leftOverlayPosition);
tooltipDirective.position = 'after';
expect(tooltipDirective._getOverlayPosition()).toEqual(rightOverlayPosition);
// Test expectations in LTR
dir.value = 'rtl';
tooltipDirective.position = 'before';
expect(tooltipDirective._getOverlayPosition()).toEqual(rightOverlayPosition);
tooltipDirective.position = 'after';
expect(tooltipDirective._getOverlayPosition()).toEqual(leftOverlayPosition);
});
it('should have consistent left transform origin in any dir', () => {
tooltipDirective.position = 'right';
tooltipDirective.show();
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('left');
tooltipDirective.position = 'after';
tooltipDirective.show();
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('left');
dir.value = 'rtl';
tooltipDirective.position = 'before';
tooltipDirective.show();
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('left');
});
it('should have consistent right transform origin in any dir', () => {
tooltipDirective.position = 'left';
tooltipDirective.show();
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('right');
tooltipDirective.position = 'before';
tooltipDirective.show();
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('right');
dir.value = 'rtl';
tooltipDirective.position = 'after';
tooltipDirective.show();
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('right');
});
});
});
+50 -19
View File
@@ -13,6 +13,7 @@ import {
animate,
AnimationTransitionEvent,
NgZone,
Optional,
} from '@angular/core';
import {
Overlay,
@@ -27,9 +28,9 @@ import {
} from '../core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {Dir} from '../core/rtl/dir';
export type TooltipPosition = 'before' | 'after' | 'above' | 'below';
export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after';
/** Time in ms to delay before changing the tooltip visibility to hidden */
export const TOUCHEND_HIDE_DELAY = 1500;
@@ -85,7 +86,8 @@ export class MdTooltip {
}
constructor(private _overlay: Overlay, private _elementRef: ElementRef,
private _viewContainerRef: ViewContainerRef, private _ngZone: NgZone) {}
private _viewContainerRef: ViewContainerRef, private _ngZone: NgZone,
@Optional() private _dir: Dir) {}
/** Dispose the tooltip when destroyed */
ngOnDestroy() {
@@ -155,22 +157,46 @@ export class MdTooltip {
}
/** Returns the origin position based on the user's position preference */
private _getOrigin(): OriginConnectionPosition {
switch (this.position) {
case 'before': return { originX: 'start', originY: 'center' };
case 'after': return { originX: 'end', originY: 'center' };
case 'above': return { originX: 'center', originY: 'top' };
case 'below': return { originX: 'center', originY: 'bottom' };
_getOrigin(): OriginConnectionPosition {
if (this.position == 'above' || this.position == 'below') {
return {originX: 'center', originY: this.position == 'above' ? 'top' : 'bottom'};
}
const isDirectionLtr = !this._dir || this._dir.value == 'ltr';
if (this.position == 'left' ||
this.position == 'before' && isDirectionLtr ||
this.position == 'after' && !isDirectionLtr) {
return {originX: 'start', originY: 'center'};
}
if (this.position == 'right' ||
this.position == 'after' && isDirectionLtr ||
this.position == 'before' && !isDirectionLtr) {
return {originX: 'end', originY: 'center'};
}
}
/** Returns the overlay position based on the user's preference */
private _getOverlayPosition(): OverlayConnectionPosition {
switch (this.position) {
case 'before': return { overlayX: 'end', overlayY: 'center' };
case 'after': return { overlayX: 'start', overlayY: 'center' };
case 'above': return { overlayX: 'center', overlayY: 'bottom' };
case 'below': return { overlayX: 'center', overlayY: 'top' };
_getOverlayPosition(): OverlayConnectionPosition {
if (this.position == 'above') {
return {overlayX: 'center', overlayY: 'bottom'};
}
if (this.position == 'below') {
return {overlayX: 'center', overlayY: 'top'};
}
const isLtr = !this._dir || this._dir.value == 'ltr';
if (this.position == 'left' ||
this.position == 'before' && isLtr ||
this.position == 'after' && !isLtr) {
return {overlayX: 'end', overlayY: 'center'};
}
if (this.position == 'right' ||
this.position == 'after' && isLtr ||
this.position == 'before' && !isLtr) {
return {overlayX: 'start', overlayY: 'center'};
}
}
@@ -226,6 +252,8 @@ export class TooltipComponent {
/** Subject for notifying that the tooltip has been hidden from the view */
private _onHide: Subject<any> = new Subject();
constructor(@Optional() private _dir: Dir) {}
/** Shows the tooltip with an animation originating from the provided origin */
show(position: TooltipPosition): void {
this._closeOnInteraction = false;
@@ -262,11 +290,14 @@ export class TooltipComponent {
/** Sets the tooltip transform origin according to the tooltip position */
_setTransformOrigin(value: TooltipPosition) {
const isLtr = !this._dir || this._dir.value == 'ltr';
switch (value) {
case 'before': this._transformOrigin = 'right'; break;
case 'after': this._transformOrigin = 'left'; break;
case 'above': this._transformOrigin = 'bottom'; break;
case 'below': this._transformOrigin = 'top'; break;
case 'before': this._transformOrigin = isLtr ? 'right' : 'left'; break;
case 'after': this._transformOrigin = isLtr ? 'left' : 'right'; break;
case 'left': this._transformOrigin = 'right'; break;
case 'right': this._transformOrigin = 'left'; break;
case 'above': this._transformOrigin = 'bottom'; break;
case 'below': this._transformOrigin = 'top'; break;
}
}