mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(overlay): support rtl in overlays (#1593)
This commit is contained in:
@@ -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<any>,
|
||||
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 */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user