Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 7699957

Browse files
atscottThomasBurleson
authored andcommitted
fix: apply correct RTL margins
Copy bidi module from angular material cdk so there's not a new dependency.
1 parent 843a68d commit 7699957

File tree

14 files changed

+458
-8
lines changed

14 files changed

+458
-8
lines changed

src/demo-app/app/docs-layout/_module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Component} from '@angular/core';
1010
<demo-flex-attribute-values class="small-demo"></demo-flex-attribute-values>
1111
<demo-flex-offset-values class="small-demo"></demo-flex-offset-values>
1212
<demo-flex-align-self class="small-demo"></demo-flex-align-self>
13+
<demo-layout-row-direction class="small-demo"></demo-layout-row-direction>
1314
`
1415
})
1516
export class DemosLayoutAPI {
@@ -26,6 +27,7 @@ import {DemoFlexRowFillWrap} from './flexRowFillWrap.demo';
2627
import {DemoFlexAttributeValues} from './flexOtherValues.demo';
2728
import {DemoFlexOffsetValues} from './flexOffetValues.demo';
2829
import {DemoFlexAlignSelf} from './FlexAlignSelf.demo';
30+
import {DemoLayoutRowDirection} from './layoutRowWithDirection.demo';
2931

3032
@NgModule({
3133
declarations: [
@@ -37,7 +39,8 @@ import {DemoFlexAlignSelf} from './FlexAlignSelf.demo';
3739
DemoFlexRowFillWrap,
3840
DemoFlexAttributeValues,
3941
DemoFlexOffsetValues,
40-
DemoFlexAlignSelf
42+
DemoFlexAlignSelf,
43+
DemoLayoutRowDirection,
4144
],
4245
imports: [
4346
SharedModule,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {Component} from '@angular/core';
2+
3+
@Component({
4+
moduleId: module.id,
5+
selector: 'demo-layout-row-direction',
6+
template: `
7+
<mat-card class="card-demo">
8+
<mat-card-title>Direction support for RTL</mat-card-title>
9+
<mat-card-subtitle>
10+
Simple row using layout gap and flex offset to demonstrate changes
11+
in layout direction between rtl and ltr.
12+
</mat-card-subtitle>
13+
<mat-card-content fxLayout="column" fxLayoutGap="8px">
14+
<div>
15+
<button (click)="toggleDirection()" mat-raised-button>
16+
Toggle direction
17+
</button>
18+
</div>
19+
<div class="containerX">
20+
<div fxLayout="row" class="colored box" fxLayoutGap="20px"
21+
[dir]="direction" fxFlex>
22+
<div fxFlexOffset="20px">item 1</div>
23+
<div>item 2</div>
24+
<div>item 3</div>
25+
</div>
26+
</div>
27+
</mat-card-content>
28+
<mat-card-footer>
29+
<div class="hint">
30+
&lt;div dir="{{ direction }}" fxLayoutGap="20px"&gt;
31+
</div>
32+
</mat-card-footer>
33+
</mat-card>
34+
`
35+
})
36+
export class DemoLayoutRowDirection {
37+
direction = 'ltr';
38+
39+
toggleDirection() {
40+
this.direction = this.direction === 'ltr' ? 'rtl' : 'ltr';
41+
}
42+
}

src/lib/api/flexbox/flex-offset.spec.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {Component} from '@angular/core';
99
import {CommonModule} from '@angular/common';
1010
import {ComponentFixture, TestBed} from '@angular/core/testing';
1111

12+
import {DIR_DOCUMENT} from '../../bidi/directionality';
1213
import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../media-query/breakpoints/break-points-provider';
1314
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';
1415
import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
@@ -27,20 +28,23 @@ import {
2728
describe('flex directive', () => {
2829
let fixture: ComponentFixture<any>;
2930
let expectDOMFrom = makeExpectDOMFrom(() => TestFlexComponent);
31+
let fakeDocument: {body: {dir?: string}, documentElement: {dir?: string}};
3032
let componentWithTemplate = (template: string) => {
3133
fixture = makeCreateTestComponent(() => TestFlexComponent)(template);
3234
};
3335

3436
beforeEach(() => {
3537
jasmine.addMatchers(customMatchers);
38+
fakeDocument = {body: {}, documentElement: {}};
3639

3740
// Configure testbed to prepare services
3841
TestBed.configureTestingModule({
3942
imports: [CommonModule, FlexLayoutModule],
4043
declarations: [TestFlexComponent],
4144
providers: [
4245
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
43-
{provide: MatchMedia, useClass: MockMatchMedia}
46+
{provide: MatchMedia, useClass: MockMatchMedia},
47+
{provide: DIR_DOCUMENT, useValue: fakeDocument}
4448
]
4549
});
4650
});
@@ -134,6 +138,44 @@ describe('flex directive', () => {
134138
});
135139
});
136140

141+
it('should set margin-right for rtl layouts on document body', () => {
142+
fakeDocument.body.dir = 'rtl';
143+
componentWithTemplate(`
144+
<div fxLayout='row' class='test'>
145+
<div fxFlex='30px' fxFlexOffset='17px'> </div>
146+
</div>
147+
`);
148+
fixture.detectChanges();
149+
150+
let element = queryFor(fixture, '[fxFlex]')[0].nativeElement;
151+
expect(element).toHaveStyle({'margin-right': '17px'});
152+
});
153+
154+
it('should set margin-right for rtl layouts on documentElement', () => {
155+
fakeDocument.documentElement.dir = 'rtl';
156+
componentWithTemplate(`
157+
<div fxLayout='row' class='test'>
158+
<div fxFlex='30px' fxFlexOffset='17px'> </div>
159+
</div>
160+
`);
161+
fixture.detectChanges();
162+
163+
let element = queryFor(fixture, '[fxFlex]')[0].nativeElement;
164+
expect(element).toHaveStyle({'margin-right': '17px'});
165+
});
166+
167+
it('should set margin-left for ltr layouts', () => {
168+
componentWithTemplate(`
169+
<div fxLayout='row' class='test'>
170+
<div fxFlex='30px' fxFlexOffset='17px'> </div>
171+
</div>
172+
`);
173+
fixture.detectChanges();
174+
175+
let element = queryFor(fixture, '[fxFlex]')[0].nativeElement;
176+
expect(element).toHaveStyle({'margin-left': '17px'});
177+
});
178+
137179
});
138180

139181
});

src/lib/api/flexbox/flex-offset.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import {Subscription} from 'rxjs/Subscription';
2424

2525
import {BaseFxDirective} from '../core/base';
26+
import {Directionality} from '../../bidi/directionality';
2627
import {MediaChange} from '../../media-query/media-change';
2728
import {MediaMonitor} from '../../media-query/media-monitor';
2829
import {LayoutDirective} from './layout';
@@ -39,6 +40,7 @@ import {isFlowHorizontal} from '../../utils/layout-validator';
3940
[fxFlexOffset.gt-xs], [fxFlexOffset.gt-sm], [fxFlexOffset.gt-md], [fxFlexOffset.gt-lg]
4041
`})
4142
export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
43+
private _directionWatcher: Subscription;
4244

4345
/* tslint:disable */
4446
@Input('fxFlexOffset') set offset(val) { this._cacheInput('offset', val); }
@@ -63,8 +65,11 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
6365
elRef: ElementRef,
6466
renderer: Renderer2,
6567
@Optional() @SkipSelf() protected _container: LayoutDirective,
66-
@Inject(PLATFORM_ID) platformId: Object) {
68+
@Inject(PLATFORM_ID) platformId: Object,
69+
private _directionality: Directionality) {
6770
super(monitor, elRef, renderer, platformId);
71+
this._directionWatcher =
72+
this._directionality.change.subscribe(this._updateWithValue.bind(this));
6873

6974

7075
this.watchParentFlow();
@@ -91,6 +96,9 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
9196
if (this._layoutWatcher) {
9297
this._layoutWatcher.unsubscribe();
9398
}
99+
if (this._directionWatcher) {
100+
this._directionWatcher.unsubscribe();
101+
}
94102
}
95103

96104
/**
@@ -162,8 +170,11 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
162170
offset = offset + '%';
163171
}
164172

173+
const horizontalLayoutKey =
174+
this._directionality.value === 'rtl' ? 'margin-right' : 'margin-left';
165175
// The flex-direction of this element's flex container. Defaults to 'row'.
166176
let layout = this._getFlowDirection(this.parentElement, true);
167-
return isFlowHorizontal(layout) ? {'margin-left': `${offset}`} : {'margin-top': `${offset}`};
177+
return isFlowHorizontal(layout) ? {[horizontalLayoutKey]: `${offset}`} :
178+
{'margin-top': `${offset}`};
168179
}
169180
}

src/lib/api/flexbox/layout-gap.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {Component, OnInit} from '@angular/core';
99
import {CommonModule} from '@angular/common';
1010
import {TestBed, ComponentFixture, async} from '@angular/core/testing';
1111

12+
import {DIR_DOCUMENT} from '../../bidi/directionality';
1213
import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../media-query/breakpoints/break-points-provider';
1314
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';
1415
import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
@@ -26,17 +27,20 @@ describe('layout-gap directive', () => {
2627
let fixture: ComponentFixture<any>;
2728
let createTestComponent = makeCreateTestComponent(() => TestLayoutGapComponent);
2829
let expectDomForQuery = makeExpectDOMForQuery(() => TestLayoutGapComponent);
30+
let fakeDocument: {body: {dir?: string}, documentElement: {dir?: string}};
2931

3032
beforeEach(() => {
3133
jasmine.addMatchers(customMatchers);
34+
fakeDocument = {body: {}, documentElement: {}};
3235

3336
// Configure testbed to prepare services
3437
TestBed.configureTestingModule({
3538
imports: [CommonModule, FlexLayoutModule],
3639
declarations: [TestLayoutGapComponent],
3740
providers: [
3841
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
39-
{provide: MatchMedia, useClass: MockMatchMedia}
42+
{provide: MatchMedia, useClass: MockMatchMedia},
43+
{provide: DIR_DOCUMENT, useValue: fakeDocument}
4044
]
4145
});
4246
});
@@ -287,6 +291,23 @@ describe('layout-gap directive', () => {
287291

288292
});
289293

294+
describe('rtl support', () => {
295+
it('uses margin-left when document body has rtl dir', () => {
296+
fakeDocument.body.dir = 'rtl';
297+
verifyCorrectMargin('row', 'margin-left');
298+
});
299+
300+
it('uses margin-left when documentElement has rtl dir', () => {
301+
fakeDocument.documentElement.dir = 'rtl';
302+
verifyCorrectMargin('row', 'margin-left');
303+
});
304+
305+
it('still uses margin-bottom in column layout when body has rtl dir', () => {
306+
fakeDocument.body.dir = 'rtl';
307+
verifyCorrectMargin('column', 'margin-bottom');
308+
});
309+
});
310+
290311
});
291312

292313

src/lib/api/flexbox/layout-gap.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {Subscription} from 'rxjs/Subscription';
2424

2525
import {BaseFxDirective} from '../core/base';
2626
import {LayoutDirective} from './layout';
27+
import {Directionality} from '../../bidi/directionality';
2728
import {MediaChange} from '../../media-query/media-change';
2829
import {MediaMonitor} from '../../media-query/media-monitor';
2930
import {LAYOUT_VALUES} from '../../utils/layout-validator';
@@ -45,6 +46,7 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
4546
protected _layout = 'row'; // default flex-direction
4647
protected _layoutWatcher: Subscription;
4748
protected _observer: MutationObserver;
49+
private _directionWatcher: Subscription;
4850

4951
/* tslint:disable */
5052
@Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); }
@@ -70,12 +72,15 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
7072
renderer: Renderer2,
7173
@Optional() @Self() container: LayoutDirective,
7274
private _zone: NgZone,
73-
@Inject(PLATFORM_ID) platformId: Object) {
75+
@Inject(PLATFORM_ID) platformId: Object,
76+
private _directionality: Directionality) {
7477
super(monitor, elRef, renderer, platformId);
7578

7679
if (container) { // Subscribe to layout direction changes
7780
this._layoutWatcher = container.layout$.subscribe(this._onLayoutChange.bind(this));
7881
}
82+
this._directionWatcher =
83+
this._directionality.change.subscribe(this._updateWithValue.bind(this));
7984
}
8085

8186
// *********************************************
@@ -108,6 +113,9 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
108113
if (this._observer) {
109114
this._observer.disconnect();
110115
}
116+
if (this._directionWatcher) {
117+
this._directionWatcher.unsubscribe();
118+
}
111119
}
112120

113121
// *********************************************
@@ -196,7 +204,7 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
196204
case 'row' :
197205
case 'row-reverse':
198206
default :
199-
key = 'margin-right';
207+
key = this._directionality.value === 'rtl' ? 'margin-left' : 'margin-right';
200208
break;
201209
}
202210
margins[key] = value;

src/lib/bidi/bidi-module.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {NgModule} from '@angular/core';
10+
import {DOCUMENT} from '@angular/common';
11+
import {Dir} from './dir';
12+
import {DIR_DOCUMENT, Directionality} from './directionality';
13+
14+
15+
@NgModule({
16+
exports: [Dir],
17+
declarations: [Dir],
18+
providers: [
19+
{provide: DIR_DOCUMENT, useExisting: DOCUMENT},
20+
Directionality,
21+
]
22+
})
23+
export class BidiModule { }

src/lib/bidi/bidi.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
The `bidi` package provides a common system for components to get and respond to change in the
2+
application's LTR/RTL layout direction. This directory was copied straight from
3+
https://github.com/angular/material2/blob/master/src/cdk/bidi/
4+
5+
### Directionality
6+
7+
When including the CDK's `BidiModule`, components can inject `Directionality` to get the current
8+
text direction (RTL or LTR);
9+
10+
#### Example
11+
```ts
12+
@Component({ ... })
13+
export class MyWidget implements OnDestroy {
14+
15+
/** Whether the widget is in RTL mode or not. */
16+
private isRtl: boolean;
17+
18+
/** Subscription to the Directionality change EventEmitter. */
19+
private _dirChangeSubscription = Subscription.EMPTY;
20+
21+
constructor(dir: Directionality) {
22+
this.isRtl = dir.value === 'rtl';
23+
24+
_dirChangeSubscription = dir.change.subscribe(() => {
25+
this.flipDirection();
26+
});
27+
}
28+
29+
ngOnDestroy() {
30+
this._dirChangeSubscription.unsubscribe();
31+
}
32+
}
33+
```
34+
35+
### The `Dir` directive
36+
The `BidiModule` also includes a directive that matches any elements with a `dir` attribute. This
37+
directive has the same API as Directionality and provides itself _as_ `Directionality`. By doing
38+
this, any component that injects `Directionality` will get the closest ancestor layout direction
39+
context.

0 commit comments

Comments
 (0)