From e07059a2217cb29a35fe9cf17fb7e1cadf0212b4 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 25 Oct 2016 11:03:58 -0700 Subject: [PATCH] feat(overlay): support rtl in overlays --- src/lib/core/overlay/overlay-directives.ts | 14 ++++++++++++-- src/lib/core/overlay/overlay-ref.ts | 6 ++++++ src/lib/core/overlay/overlay-state.ts | 4 ++++ src/lib/core/overlay/overlay.spec.ts | 10 ++++++++++ .../connected-position-strategy.spec.ts | 17 +++++++++++++++++ .../position/connected-position-strategy.ts | 16 +++++++++++++--- 6 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index 5e90541635c7..6661436ff05a 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -5,6 +5,7 @@ import { EventEmitter, TemplateRef, ViewContainerRef, + Optional, Input, OnDestroy, Output, @@ -18,6 +19,7 @@ import {ConnectionPositionPair} from './position/connected-position'; import {PortalModule} from '../portal/portal-directives'; import {ConnectedPositionStrategy} from './position/connected-position-strategy'; import {Subscription} from 'rxjs/Subscription'; +import {Dir, LayoutDirection} from '../rtl/dir'; /** Default set of positions for the overlay. Follows the behavior of a dropdown. */ let defaultPositionList = [ @@ -103,7 +105,8 @@ export class ConnectedOverlayDirective implements OnDestroy { constructor( private _overlay: Overlay, templateRef: TemplateRef, - viewContainerRef: ViewContainerRef) { + viewContainerRef: ViewContainerRef, + @Optional() private _dir: Dir) { this._templatePortal = new TemplatePortal(templateRef, viewContainerRef); } @@ -111,6 +114,10 @@ export class ConnectedOverlayDirective implements OnDestroy { return this._overlayRef; } + get dir(): LayoutDirection { + return this._dir ? this._dir.value : 'ltr'; + } + /** TODO: internal */ ngOnDestroy() { this._destroyOverlay(); @@ -145,6 +152,8 @@ export class ConnectedOverlayDirective implements OnDestroy { overlayConfig.positionStrategy = this._getPosition(); + overlayConfig.direction = this.dir; + return overlayConfig; } @@ -153,7 +162,8 @@ export class ConnectedOverlayDirective implements OnDestroy { return this._overlay.position().connectedTo( this.origin.elementRef, {originX: this.positions[0].overlayX, originY: this.positions[0].originY}, - {overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY}); + {overlayX: this.positions[0].overlayX, overlayY: this.positions[0].overlayY}) + .setDirection(this.dir); } /** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */ diff --git a/src/lib/core/overlay/overlay-ref.ts b/src/lib/core/overlay/overlay-ref.ts index 60cf3b28662d..4ebaa2d2745e 100644 --- a/src/lib/core/overlay/overlay-ref.ts +++ b/src/lib/core/overlay/overlay-ref.ts @@ -24,6 +24,7 @@ export class OverlayRef implements PortalHost { let attachResult = this._portalHost.attach(portal); this.updateSize(); + this.updateDirection(); this.updatePosition(); return attachResult; @@ -59,6 +60,11 @@ export class OverlayRef implements PortalHost { } } + /** Updates the text direction of the overlay panel. **/ + private updateDirection() { + this._pane.setAttribute('dir', this._state.direction); + } + /** Updates the size of the overlay based on the overlay config. */ updateSize() { if (this._state.width || this._state.width === 0) { diff --git a/src/lib/core/overlay/overlay-state.ts b/src/lib/core/overlay/overlay-state.ts index dd6486f0cac0..61b07fb05a16 100644 --- a/src/lib/core/overlay/overlay-state.ts +++ b/src/lib/core/overlay/overlay-state.ts @@ -1,4 +1,5 @@ import {PositionStrategy} from './position/position-strategy'; +import {LayoutDirection} from '../rtl/dir'; /** @@ -21,6 +22,9 @@ export class OverlayState { /** The height of the overlay panel. If a number is provided, pixel units are assumed. **/ height: number | string; + /** The direction of the text in the overlay panel. */ + direction: LayoutDirection = 'ltr'; + // TODO(jelbourn): configuration still to add // - focus trap // - disable pointer events diff --git a/src/lib/core/overlay/overlay.spec.ts b/src/lib/core/overlay/overlay.spec.ts index 2f896c4d8a2d..b0b63402c4d5 100644 --- a/src/lib/core/overlay/overlay.spec.ts +++ b/src/lib/core/overlay/overlay.spec.ts @@ -82,6 +82,16 @@ describe('Overlay', () => { expect(overlayContainerElement.textContent).toBe(''); }); + it('should set the direction', () => { + const state = new OverlayState(); + state.direction = 'rtl'; + + overlay.create(state).attach(componentPortal); + + const pane = overlayContainerElement.children[0] as HTMLElement; + expect(pane.getAttribute('dir')).toEqual('rtl'); + }); + describe('positioning', () => { let state: OverlayState; diff --git a/src/lib/core/overlay/position/connected-position-strategy.spec.ts b/src/lib/core/overlay/position/connected-position-strategy.spec.ts index e5a47c22fb99..1ef65ee3d7cb 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.spec.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.spec.ts @@ -206,6 +206,23 @@ describe('ConnectedPositionStrategy', () => { expect(overlayRect.top).toBe(originRect.bottom); expect(overlayRect.right).toBe(originRect.left); }); + + it('should position a panel properly when rtl', () => { + // must make the overlay longer than the origin to properly test attachment + overlayElement.style.width = `500px`; + originRect = originElement.getBoundingClientRect(); + strategy = positionBuilder.connectedTo( + fakeElementRef, + {originX: 'start', originY: 'bottom'}, + {overlayX: 'start', overlayY: 'top'}) + .setDirection('rtl'); + + strategy.apply(overlayElement); + + let overlayRect = overlayElement.getBoundingClientRect(); + expect(overlayRect.top).toBe(originRect.bottom); + expect(overlayRect.right).toBe(originRect.right); + }); }); diff --git a/src/lib/core/overlay/position/connected-position-strategy.ts b/src/lib/core/overlay/position/connected-position-strategy.ts index 89054ea84938..d46f1d48f353 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.ts @@ -17,9 +17,12 @@ import { * of the overlay. */ export class ConnectedPositionStrategy implements PositionStrategy { - // TODO(jelbourn): set RTL to the actual value from the app. + private _dir = 'ltr'; + /** Whether the we're dealing with an RTL context */ - _isRtl: boolean = false; + get _isRtl() { + return this._dir === 'rtl'; + } /** Ordered list of preferred positions, from most to least desirable. */ _preferredPositions: ConnectionPositionPair[] = []; @@ -85,6 +88,11 @@ export class ConnectedPositionStrategy implements PositionStrategy { return this; } + /** Sets the layout direction so the overlay's position can be adjusted to match. */ + setDirection(dir: 'ltr' | 'rtl') { + this._dir = dir; + return this; + } /** * 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 { let overlayStartX: number; if (pos.overlayX == 'center') { overlayStartX = -overlayRect.width / 2; + } else if (pos.overlayX === 'start') { + overlayStartX = this._isRtl ? -overlayRect.width : 0; } else { - overlayStartX = pos.overlayX == 'start' ? 0 : -overlayRect.width; + overlayStartX = this._isRtl ? 0 : -overlayRect.width; } let overlayStartY: number;