Skip to content

Commit 56ba638

Browse files
committed
feat(overlay): overlay rtl support
1 parent f6d0e45 commit 56ba638

File tree

6 files changed

+61
-5
lines changed

6 files changed

+61
-5
lines changed

src/lib/core/overlay/overlay-directives.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
EventEmitter,
66
TemplateRef,
77
ViewContainerRef,
8+
Optional,
89
Input,
910
OnDestroy,
1011
Output,
@@ -18,6 +19,7 @@ import {ConnectionPositionPair} from './position/connected-position';
1819
import {PortalModule} from '../portal/portal-directives';
1920
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
2021
import {Subscription} from 'rxjs/Subscription';
22+
import {Dir} from '../rtl/dir';
2123

2224
/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
2325
let defaultPositionList = [
@@ -102,14 +104,19 @@ export class ConnectedOverlayDirective implements OnDestroy {
102104
constructor(
103105
private _overlay: Overlay,
104106
templateRef: TemplateRef<any>,
105-
viewContainerRef: ViewContainerRef) {
107+
viewContainerRef: ViewContainerRef,
108+
@Optional() private _dir: Dir) {
106109
this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
107110
}
108111

109112
get overlayRef(): OverlayRef {
110113
return this._overlayRef;
111114
}
112115

116+
get dir(): 'ltr' | 'rtl' {
117+
return this._dir ? this._dir.value : 'ltr';
118+
}
119+
113120
/** TODO: internal */
114121
ngOnDestroy() {
115122
this._destroyOverlay();
@@ -144,6 +151,8 @@ export class ConnectedOverlayDirective implements OnDestroy {
144151

145152
overlayConfig.positionStrategy = this._getPosition();
146153

154+
overlayConfig.direction = this.dir;
155+
147156
return overlayConfig;
148157
}
149158

@@ -152,7 +161,8 @@ export class ConnectedOverlayDirective implements OnDestroy {
152161
return this._overlay.position().connectedTo(
153162
this.origin.elementRef,
154163
{originX: this.positions[0].overlayX, originY: this.positions[0].originY},
155-
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY});
164+
{overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY})
165+
.setDirection(this.dir);
156166
}
157167

158168
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */

src/lib/core/overlay/overlay-ref.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class OverlayRef implements PortalHost {
2424

2525
let attachResult = this._portalHost.attach(portal);
2626
this.updateSize();
27+
this.updateDirection();
2728
this.updatePosition();
2829

2930
return attachResult;
@@ -59,6 +60,11 @@ export class OverlayRef implements PortalHost {
5960
}
6061
}
6162

63+
/** Updates the text direction of the overlay panel. **/
64+
private updateDirection() {
65+
this._pane.style.direction = this._state.direction;
66+
}
67+
6268
/** Updates the size of the overlay based on the overlay config. */
6369
updateSize() {
6470
if (this._state.width || this._state.width === 0) {

src/lib/core/overlay/overlay-state.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export class OverlayState {
2121
/** The height of the overlay panel. If a number is provided, pixel units are assumed. **/
2222
height: number | string;
2323

24+
/** The direction of the text in the overlay panel. */
25+
direction: 'ltr' | 'rtl' = 'ltr';
26+
2427
// TODO(jelbourn): configuration still to add
2528
// - focus trap
2629
// - disable pointer events

src/lib/core/overlay/overlay.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ describe('Overlay', () => {
8282
expect(overlayContainerElement.textContent).toBe('');
8383
});
8484

85+
it('should set the direction', () => {
86+
const state = new OverlayState();
87+
state.direction = 'rtl';
88+
89+
overlay.create(state).attach(componentPortal);
90+
91+
const pane = overlayContainerElement.children[0] as HTMLElement;
92+
expect(pane.style.direction).toEqual('rtl');
93+
});
94+
8595
describe('positioning', () => {
8696
let state: OverlayState;
8797

src/lib/core/overlay/position/connected-position-strategy.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,23 @@ describe('ConnectedPositionStrategy', () => {
206206
expect(overlayRect.top).toBe(originRect.bottom);
207207
expect(overlayRect.right).toBe(originRect.left);
208208
});
209+
210+
it('should position a panel properly when rtl', () => {
211+
// must make the overlay longer than the origin to properly test attachment
212+
overlayElement.style.width = `500px`;
213+
originRect = originElement.getBoundingClientRect();
214+
strategy = positionBuilder.connectedTo(
215+
fakeElementRef,
216+
{originX: 'start', originY: 'bottom'},
217+
{overlayX: 'start', overlayY: 'top'})
218+
.setDirection('rtl');
219+
220+
strategy.apply(overlayElement);
221+
222+
let overlayRect = overlayElement.getBoundingClientRect();
223+
expect(overlayRect.top).toBe(originRect.bottom);
224+
expect(overlayRect.right).toBe(originRect.right);
225+
});
209226
});
210227

211228

src/lib/core/overlay/position/connected-position-strategy.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ import {
1717
* of the overlay.
1818
*/
1919
export class ConnectedPositionStrategy implements PositionStrategy {
20-
// TODO(jelbourn): set RTL to the actual value from the app.
20+
private _dir = 'ltr';
21+
2122
/** Whether the we're dealing with an RTL context */
22-
_isRtl: boolean = false;
23+
get _isRtl() {
24+
return this._dir === 'rtl';
25+
}
2326

2427
/** Ordered list of preferred positions, from most to least desirable. */
2528
_preferredPositions: ConnectionPositionPair[] = [];
@@ -85,6 +88,11 @@ export class ConnectedPositionStrategy implements PositionStrategy {
8588
return this;
8689
}
8790

91+
/** Sets the layout direction so the overlay's position can be adjusted to match. */
92+
setDirection(dir: 'ltr' | 'rtl') {
93+
this._dir = dir;
94+
return this;
95+
}
8896

8997
/**
9098
* Gets the horizontal (x) "start" dimension based on whether the overlay is in an RTL context.
@@ -146,8 +154,10 @@ export class ConnectedPositionStrategy implements PositionStrategy {
146154
let overlayStartX: number;
147155
if (pos.overlayX == 'center') {
148156
overlayStartX = -overlayRect.width / 2;
157+
} else if (pos.overlayX === 'start') {
158+
overlayStartX = this._isRtl ? -overlayRect.width : 0;
149159
} else {
150-
overlayStartX = pos.overlayX == 'start' ? 0 : -overlayRect.width;
160+
overlayStartX = this._isRtl ? 0 : -overlayRect.width;
151161
}
152162

153163
let overlayStartY: number;

0 commit comments

Comments
 (0)