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

Commit 00cd78a

Browse files
fix(fxFlexOffset): use parent flow direction for margin property
`fxFlexOffset` assigns inline `margin-left` style; assuming default flow directions == 'row'. If the parent flow direction == 'column', then a `margin-top` should be used. Also added a subscription to an optional parent element LayoutDirective; which will trigger the FlexOffsetDirective to update the inline styles to match the current flow direction. > Note: if `fxFlexOffset` is used **without** a parent flexbox styling (set via css or directive), then a `display:flex; flex-direction:row;` will be auto assigned to the fxFlexOffset host element's parent. Fixes #328.
1 parent 37a0b85 commit 00cd78a

File tree

4 files changed

+230
-6
lines changed

4 files changed

+230
-6
lines changed

src/lib/flexbox/api/base.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
7373
// Accessor Methods
7474
// *********************************************
7575

76+
/**
77+
* Access to host element's parent DOM node
78+
*/
79+
protected get parentElement(): any {
80+
return this._elementRef.nativeElement.parentNode;
81+
}
82+
7683
/**
7784
* Access the current value (if any) of the @Input property.
7885
*/
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. 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+
import {Component} from '@angular/core';
9+
import {CommonModule} from '@angular/common';
10+
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
11+
12+
import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../media-query/breakpoints/break-points-provider';
13+
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';
14+
import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
15+
import {MatchMedia} from '../../media-query/match-media';
16+
import {FlexLayoutModule} from '../../module';
17+
18+
import {customMatchers, expect} from '../../utils/testing/custom-matchers';
19+
import {_dom as _} from '../../utils/testing/dom-tools';
20+
21+
import {
22+
makeExpectDOMFrom,
23+
makeCreateTestComponent,
24+
queryFor
25+
} from '../../utils/testing/helpers';
26+
27+
describe('flex directive', () => {
28+
let fixture: ComponentFixture<any>;
29+
let matchMedia: MockMatchMedia;
30+
let expectDOMFrom = makeExpectDOMFrom(() => TestFlexComponent);
31+
let componentWithTemplate = (template: string) => {
32+
fixture = makeCreateTestComponent(() => TestFlexComponent)(template);
33+
34+
inject([MatchMedia], (_matchMedia: MockMatchMedia) => {
35+
matchMedia = _matchMedia;
36+
})();
37+
};
38+
39+
beforeEach(() => {
40+
jasmine.addMatchers(customMatchers);
41+
42+
// Configure testbed to prepare services
43+
TestBed.configureTestingModule({
44+
imports: [CommonModule, FlexLayoutModule],
45+
declarations: [TestFlexComponent],
46+
providers: [
47+
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
48+
{provide: MatchMedia, useClass: MockMatchMedia}
49+
]
50+
});
51+
});
52+
53+
describe('with static features', () => {
54+
55+
it('should add correct styles for default `fxFlexOffset` usage', () => {
56+
componentWithTemplate(`<div fxFlexOffset='32px' fxFlex></div>`);
57+
fixture.detectChanges();
58+
59+
let dom = fixture.debugElement.children[0].nativeElement;
60+
let isBox = _.hasStyle(dom, 'margin-left', '32px');
61+
let hasFlex = _.hasStyle(dom, 'flex', '1 1 1e-09px') || // IE
62+
_.hasStyle(dom, 'flex', '1 1 1e-9px') || // Chrome
63+
_.hasStyle(dom, 'flex', '1 1 0.000000001px') || // Safari
64+
_.hasStyle(dom, 'flex', '1 1 0px');
65+
66+
expect(isBox).toBeTruthy();
67+
expect(hasFlex).toBeTruthy();
68+
});
69+
70+
71+
it('should work with percentage values', () => {
72+
expectDOMFrom(`<div fxFlexOffset='17' fxFlex='37'></div>`).toHaveCssStyle({
73+
'flex': '1 1 100%',
74+
'box-sizing': 'border-box',
75+
'margin-left': '17%'
76+
});
77+
});
78+
79+
it('should work fxLayout parents', () => {
80+
componentWithTemplate(`
81+
<div fxLayout='column' class='test'>
82+
<div fxFlex='30px' fxFlexOffset='17px'> </div>
83+
</div>
84+
`);
85+
fixture.detectChanges();
86+
let parent = queryFor(fixture, '.test')[0].nativeElement;
87+
let element = queryFor(fixture, '[fxFlex]')[0].nativeElement;
88+
89+
// parent flex-direction found with 'column' with child height styles
90+
expect(parent).toHaveCssStyle({'flex-direction': 'column', 'display': 'flex'});
91+
expect(element).toHaveCssStyle({'margin-top': '17px'});
92+
});
93+
94+
it('should CSS stylesheet and not inject flex-direction on parent', () => {
95+
componentWithTemplate(`
96+
<style>
97+
.test { flex-direction:column; display: flex; }
98+
</style>
99+
<div class='test'>
100+
<div fxFlexOffset='41px' fxFlex='30px'></div>
101+
</div>
102+
`);
103+
104+
fixture.detectChanges();
105+
let parent = queryFor(fixture, '.test')[0].nativeElement;
106+
let element = queryFor(fixture, '[fxFlex]')[0].nativeElement;
107+
108+
// parent flex-direction found with 'column' with child height styles
109+
expect(parent).toHaveCssStyle({'flex-direction': 'column', 'display': 'flex'});
110+
expect(element).toHaveCssStyle({'margin-top': '41px'});
111+
});
112+
113+
it('should work with styled-parent flex directions', () => {
114+
componentWithTemplate(`
115+
<div fxLayout='row'>
116+
<div style='flex-direction:column' class='parent'>
117+
<div fxFlex='60px' fxFlexOffset='21'> </div>
118+
</div>
119+
</div>
120+
`);
121+
fixture.detectChanges();
122+
let element = queryFor(fixture, '[fxFlex]')[0].nativeElement;
123+
let parent = queryFor(fixture, '.parent')[0].nativeElement;
124+
125+
// parent flex-direction found with 'column'; set child with height styles
126+
expect(element).toHaveCssStyle({'margin-top': '21%'});
127+
expect(parent).toHaveCssStyle({'flex-direction': 'column'});
128+
});
129+
130+
131+
describe('', () => {
132+
133+
it('should ignore fxLayout settings on same element', () => {
134+
expectDOMFrom(`
135+
<div fxLayout='column' fxFlex='37%' fxFlexOffset='52px' >
136+
</div>
137+
`)
138+
.not.toHaveCssStyle({
139+
'flex-direction': 'row',
140+
'flex': '1 1 100%',
141+
'margin-left': '52px',
142+
});
143+
});
144+
145+
});
146+
});
147+
148+
});
149+
150+
151+
// *****************************************************************
152+
// Template Component
153+
// *****************************************************************
154+
155+
@Component({
156+
selector: 'test-component-shell',
157+
template: `<span>PlaceHolder Template HTML</span>`
158+
})
159+
export class TestFlexComponent {
160+
public direction = 'column';
161+
}
162+
163+

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

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ import {
1212
OnInit,
1313
OnChanges,
1414
OnDestroy,
15+
Optional,
1516
Renderer,
1617
SimpleChanges,
18+
SkipSelf
1719
} from '@angular/core';
1820

21+
import {Subscription} from 'rxjs/Subscription';
1922

2023
import {BaseFxDirective} from './base';
2124
import {MediaChange} from '../../media-query/media-change';
2225
import {MediaMonitor} from '../../media-query/media-monitor';
23-
26+
import {LayoutDirective} from './layout';
2427

2528
/**
2629
* 'flex-offset' flexbox styling directive
@@ -53,8 +56,14 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
5356
@Input('fxFlexOffset.gt-lg') set offsetGtLg(val) { this._cacheInput('offsetGtLg', val); };
5457

5558
/* tslint:enable */
56-
constructor(monitor: MediaMonitor, elRef: ElementRef, renderer: Renderer) {
59+
constructor(monitor: MediaMonitor,
60+
elRef: ElementRef,
61+
renderer: Renderer,
62+
@Optional() @SkipSelf() protected _container: LayoutDirective ) {
5763
super(monitor, elRef, renderer);
64+
65+
66+
this.watchParentFlow();
5867
}
5968

6069
// *********************************************
@@ -70,6 +79,16 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
7079
}
7180
}
7281

82+
/**
83+
* Cleanup
84+
*/
85+
ngOnDestroy() {
86+
super.ngOnDestroy();
87+
if (this._layoutWatcher) {
88+
this._layoutWatcher.unsubscribe();
89+
}
90+
}
91+
7392
/**
7493
* After the initial onChanges, build an mqActivation object that bridges
7594
* mql change events to onMediaQueryChange handlers
@@ -84,7 +103,43 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
84103
// Protected methods
85104
// *********************************************
86105

106+
/** The flex-direction of this element's host container. Defaults to 'row'. */
107+
protected _layout = 'row';
108+
109+
/**
110+
* Subscription to the parent flex container's layout changes.
111+
* Stored so we can unsubscribe when this directive is destroyed.
112+
*/
113+
protected _layoutWatcher: Subscription;
114+
115+
/**
116+
* If parent flow-direction changes, then update the margin property
117+
* used to offset
118+
*/
119+
protected watchParentFlow() {
120+
if (this._container) {
121+
// Subscribe to layout immediate parent direction changes (if any)
122+
this._layoutWatcher = this._container.layout$.subscribe((direction) => {
123+
// `direction` === null if parent container does not have a `fxLayout`
124+
this._onLayoutChange(direction);
125+
});
126+
}
127+
}
87128

129+
/**
130+
* Caches the parent container's 'flex-direction' and updates the element's style.
131+
* Used as a handler for layout change events from the parent flex container.
132+
*/
133+
protected _onLayoutChange(direction?: string) {
134+
this._layout = direction || this._layout || 'row';
135+
this._updateWithValue();
136+
}
137+
138+
/**
139+
* Using the current fxFlexOffset value, update the inline CSS
140+
* NOTE: this will assign `margin-left` if the parent flex-direction == 'row',
141+
* otherwise `margin-top` is used for the offset.
142+
*/
88143
protected _updateWithValue(value?: string|number) {
89144
value = value || this._queryInput('offset') || 0;
90145
if (this._mqActivation) {
@@ -101,6 +156,8 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
101156
offset = offset + '%';
102157
}
103158

104-
return {'margin-left': `${offset}`};
159+
// The flex-direction of this element's flex container. Defaults to 'row'.
160+
let layout = this._getFlowDirection(this.parentElement, true);
161+
return layout == 'row' ? {'margin-left': `${offset}`} : {'margin-top': `${offset}`};
105162
}
106163
}

src/lib/flexbox/api/flex.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,4 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
254254
return extendObject(css, {'box-sizing': 'border-box'});
255255
}
256256

257-
protected get parentElement(): any {
258-
return this._elementRef.nativeElement.parentNode;
259-
}
260257
}

0 commit comments

Comments
 (0)