Skip to content

Commit 54bf6ce

Browse files
andrewseguinmmalerba
authored andcommitted
fix(tooltip) add RTL support (#1830)
* feat(tooltip): support rtl * remove fdescribe * more changes for before, after * fix test * rebase * minor change
1 parent 5cf7d17 commit 54bf6ce

File tree

4 files changed

+135
-24
lines changed

4 files changed

+135
-24
lines changed

src/demo-app/tooltip/tooltip-demo.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ <h1>Tooltip Demo</h1>
1515
<md-radio-group [(ngModel)]="position">
1616
<md-radio-button value="below">Below</md-radio-button>
1717
<md-radio-button value="above">Above</md-radio-button>
18+
<md-radio-button value="left">Left</md-radio-button>
19+
<md-radio-button value="right">Right</md-radio-button>
1820
<md-radio-button value="before">Before</md-radio-button>
1921
<md-radio-button value="after">After</md-radio-button>
2022
</md-radio-group>

src/lib/tooltip/README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
# MdTooltip
2-
Tooltip allows the user to specify text to be displayed when the mouse hover over an element.
2+
Tooltip allows the user to specify text to be displayed when the mouse hovers over an element.
3+
4+
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.
35

46
### Examples
57
A button with a tooltip
68
```html
7-
<button md-tooltip="some message" tooltip-position="below">Button</button>
9+
<button md-tooltip="some message" tooltip-position="bottom">Button</button>
810
```
911

12+
13+
1014
## `[md-tooltip]`
1115
### Properties
1216

1317
| Name | Type | Description |
1418
| --- | --- | --- |
1519
| `md-tooltip` | `string` | The message to be displayed. |
16-
| `tooltip-position` | `"above"|"below"|"before"|"after"` | The position of the tooltip. |
20+
| `tooltip-position` | `"before"|"after"|"above"|"below"|"left"|"right"` | The position of the tooltip. |
1721

1822
### Methods
1923

2024
| Name | Description |
21-
| --- | --- | --- |
25+
| --- | --- |
2226
| `show` | Displays the tooltip. |
2327
| `hide` | Removes the tooltip. |
2428
| `toggle` | Displays or hides the tooltip. |

src/lib/tooltip/tooltip.spec.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import {Component, DebugElement, AnimationTransitionEvent} from '@angular/core';
1010
import {By} from '@angular/platform-browser';
1111
import {TooltipPosition, MdTooltip, MdTooltipModule} from './tooltip';
1212
import {OverlayContainer} from '../core';
13+
import {Dir, LayoutDirection} from '../core/rtl/dir';
1314

1415
const initialTooltipMessage = 'initial tooltip message';
1516

1617
describe('MdTooltip', () => {
1718
let overlayContainerElement: HTMLElement;
18-
19+
let dir: {value: LayoutDirection};
1920

2021
beforeEach(async(() => {
2122
TestBed.configureTestingModule({
@@ -25,6 +26,9 @@ describe('MdTooltip', () => {
2526
{provide: OverlayContainer, useFactory: () => {
2627
overlayContainerElement = document.createElement('div');
2728
return {getContainerElement: () => overlayContainerElement};
29+
}},
30+
{provide: Dir, useFactory: () => {
31+
return dir = { value: 'ltr' };
2832
}}
2933
]
3034
});
@@ -155,6 +159,76 @@ describe('MdTooltip', () => {
155159
phaseName: '',
156160
}));
157161
}));
162+
163+
it('should consistently position before and after overlay origin in ltr and rtl dir', () => {
164+
tooltipDirective.position = 'left';
165+
const leftOrigin = tooltipDirective._getOrigin();
166+
tooltipDirective.position = 'right';
167+
const rightOrigin = tooltipDirective._getOrigin();
168+
169+
// Test expectations in LTR
170+
tooltipDirective.position = 'before';
171+
expect(tooltipDirective._getOrigin()).toEqual(leftOrigin);
172+
tooltipDirective.position = 'after';
173+
expect(tooltipDirective._getOrigin()).toEqual(rightOrigin);
174+
175+
// Test expectations in LTR
176+
dir.value = 'rtl';
177+
tooltipDirective.position = 'before';
178+
expect(tooltipDirective._getOrigin()).toEqual(rightOrigin);
179+
tooltipDirective.position = 'after';
180+
expect(tooltipDirective._getOrigin()).toEqual(leftOrigin);
181+
});
182+
183+
it('should consistently position before and after overlay position in ltr and rtl dir', () => {
184+
tooltipDirective.position = 'left';
185+
const leftOverlayPosition = tooltipDirective._getOverlayPosition();
186+
tooltipDirective.position = 'right';
187+
const rightOverlayPosition = tooltipDirective._getOverlayPosition();
188+
189+
// Test expectations in LTR
190+
tooltipDirective.position = 'before';
191+
expect(tooltipDirective._getOverlayPosition()).toEqual(leftOverlayPosition);
192+
tooltipDirective.position = 'after';
193+
expect(tooltipDirective._getOverlayPosition()).toEqual(rightOverlayPosition);
194+
195+
// Test expectations in LTR
196+
dir.value = 'rtl';
197+
tooltipDirective.position = 'before';
198+
expect(tooltipDirective._getOverlayPosition()).toEqual(rightOverlayPosition);
199+
tooltipDirective.position = 'after';
200+
expect(tooltipDirective._getOverlayPosition()).toEqual(leftOverlayPosition);
201+
});
202+
203+
it('should have consistent left transform origin in any dir', () => {
204+
tooltipDirective.position = 'right';
205+
tooltipDirective.show();
206+
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('left');
207+
208+
tooltipDirective.position = 'after';
209+
tooltipDirective.show();
210+
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('left');
211+
212+
dir.value = 'rtl';
213+
tooltipDirective.position = 'before';
214+
tooltipDirective.show();
215+
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('left');
216+
});
217+
218+
it('should have consistent right transform origin in any dir', () => {
219+
tooltipDirective.position = 'left';
220+
tooltipDirective.show();
221+
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('right');
222+
223+
tooltipDirective.position = 'before';
224+
tooltipDirective.show();
225+
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('right');
226+
227+
dir.value = 'rtl';
228+
tooltipDirective.position = 'after';
229+
tooltipDirective.show();
230+
expect(tooltipDirective._tooltipInstance._transformOrigin).toBe('right');
231+
});
158232
});
159233
});
160234

src/lib/tooltip/tooltip.ts

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
animate,
1414
AnimationTransitionEvent,
1515
NgZone,
16+
Optional,
1617
} from '@angular/core';
1718
import {
1819
Overlay,
@@ -27,9 +28,9 @@ import {
2728
} from '../core';
2829
import {Observable} from 'rxjs/Observable';
2930
import {Subject} from 'rxjs/Subject';
31+
import {Dir} from '../core/rtl/dir';
3032

31-
32-
export type TooltipPosition = 'before' | 'after' | 'above' | 'below';
33+
export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after';
3334

3435
/** Time in ms to delay before changing the tooltip visibility to hidden */
3536
export const TOUCHEND_HIDE_DELAY = 1500;
@@ -85,7 +86,8 @@ export class MdTooltip {
8586
}
8687

8788
constructor(private _overlay: Overlay, private _elementRef: ElementRef,
88-
private _viewContainerRef: ViewContainerRef, private _ngZone: NgZone) {}
89+
private _viewContainerRef: ViewContainerRef, private _ngZone: NgZone,
90+
@Optional() private _dir: Dir) {}
8991

9092
/** Dispose the tooltip when destroyed */
9193
ngOnDestroy() {
@@ -155,22 +157,46 @@ export class MdTooltip {
155157
}
156158

157159
/** Returns the origin position based on the user's position preference */
158-
private _getOrigin(): OriginConnectionPosition {
159-
switch (this.position) {
160-
case 'before': return { originX: 'start', originY: 'center' };
161-
case 'after': return { originX: 'end', originY: 'center' };
162-
case 'above': return { originX: 'center', originY: 'top' };
163-
case 'below': return { originX: 'center', originY: 'bottom' };
160+
_getOrigin(): OriginConnectionPosition {
161+
if (this.position == 'above' || this.position == 'below') {
162+
return {originX: 'center', originY: this.position == 'above' ? 'top' : 'bottom'};
163+
}
164+
165+
const isDirectionLtr = !this._dir || this._dir.value == 'ltr';
166+
if (this.position == 'left' ||
167+
this.position == 'before' && isDirectionLtr ||
168+
this.position == 'after' && !isDirectionLtr) {
169+
return {originX: 'start', originY: 'center'};
170+
}
171+
172+
if (this.position == 'right' ||
173+
this.position == 'after' && isDirectionLtr ||
174+
this.position == 'before' && !isDirectionLtr) {
175+
return {originX: 'end', originY: 'center'};
164176
}
165177
}
166178

167179
/** Returns the overlay position based on the user's preference */
168-
private _getOverlayPosition(): OverlayConnectionPosition {
169-
switch (this.position) {
170-
case 'before': return { overlayX: 'end', overlayY: 'center' };
171-
case 'after': return { overlayX: 'start', overlayY: 'center' };
172-
case 'above': return { overlayX: 'center', overlayY: 'bottom' };
173-
case 'below': return { overlayX: 'center', overlayY: 'top' };
180+
_getOverlayPosition(): OverlayConnectionPosition {
181+
if (this.position == 'above') {
182+
return {overlayX: 'center', overlayY: 'bottom'};
183+
}
184+
185+
if (this.position == 'below') {
186+
return {overlayX: 'center', overlayY: 'top'};
187+
}
188+
189+
const isLtr = !this._dir || this._dir.value == 'ltr';
190+
if (this.position == 'left' ||
191+
this.position == 'before' && isLtr ||
192+
this.position == 'after' && !isLtr) {
193+
return {overlayX: 'end', overlayY: 'center'};
194+
}
195+
196+
if (this.position == 'right' ||
197+
this.position == 'after' && isLtr ||
198+
this.position == 'before' && !isLtr) {
199+
return {overlayX: 'start', overlayY: 'center'};
174200
}
175201
}
176202

@@ -226,6 +252,8 @@ export class TooltipComponent {
226252
/** Subject for notifying that the tooltip has been hidden from the view */
227253
private _onHide: Subject<any> = new Subject();
228254

255+
constructor(@Optional() private _dir: Dir) {}
256+
229257
/** Shows the tooltip with an animation originating from the provided origin */
230258
show(position: TooltipPosition): void {
231259
this._closeOnInteraction = false;
@@ -262,11 +290,14 @@ export class TooltipComponent {
262290

263291
/** Sets the tooltip transform origin according to the tooltip position */
264292
_setTransformOrigin(value: TooltipPosition) {
293+
const isLtr = !this._dir || this._dir.value == 'ltr';
265294
switch (value) {
266-
case 'before': this._transformOrigin = 'right'; break;
267-
case 'after': this._transformOrigin = 'left'; break;
268-
case 'above': this._transformOrigin = 'bottom'; break;
269-
case 'below': this._transformOrigin = 'top'; break;
295+
case 'before': this._transformOrigin = isLtr ? 'right' : 'left'; break;
296+
case 'after': this._transformOrigin = isLtr ? 'left' : 'right'; break;
297+
case 'left': this._transformOrigin = 'right'; break;
298+
case 'right': this._transformOrigin = 'left'; break;
299+
case 'above': this._transformOrigin = 'bottom'; break;
300+
case 'below': this._transformOrigin = 'top'; break;
270301
}
271302
}
272303

0 commit comments

Comments
 (0)