Skip to content

Commit

Permalink
feat(fxFlex): compute immediate parent flex-direction
Browse files Browse the repository at this point in the history
Support use of **fxFlex** directive on HTML element without a **fxLayout**  parent
directive. **fxFlex** requires a `flex-direction` to be specific on the immediate parent:

  * use defined flex-direction or getComputed.
  * auto-inject **fxLayout** styles to parent if parent `flex-direction` not already provided.
  • Loading branch information
ThomasBurleson committed Mar 15, 2017
1 parent f270078 commit 2ea5dfd
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 128 deletions.
23 changes: 23 additions & 0 deletions src/lib/flexbox/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import {ElementRef, Renderer, OnDestroy} from '@angular/core';

import {applyCssPrefixes} from '../../utils/auto-prefixer';
import {buildLayoutCSS} from '../../utils/layout-validator';

import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation';
import {MediaMonitor} from '../../media-query/media-monitor';
Expand Down Expand Up @@ -93,6 +94,28 @@ export abstract class BaseFxDirective implements OnDestroy {
return value.trim();
}

protected _getParentFlowDirection(source?: HTMLElement, addIfMissing = true): string {
let value = "";
let element: HTMLElement = source || this._elementRef.nativeElement;
let parent = element ? element.parentNode : null;

if ( parent ) {
let directionKeys = Object.keys(applyCssPrefixes({'flex-direction': ''}));
let findDirection = (target) => directionKeys.reduce((direction, key) => {
return direction || target[key];
}, null);

let immediateValue = findDirection(parent['style']);
value = immediateValue || findDirection(getComputedStyle(parent as Element));
if ( !immediateValue && addIfMissing ) {
value = value || 'row';
this._applyStyleToElements(buildLayoutCSS(value), [element.parentElement]);
}
}

return value ? value.trim() : "";
}

/**
* Applies styles given via string pair or object map to the directive element.
*/
Expand Down
52 changes: 52 additions & 0 deletions src/lib/flexbox/api/flex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,58 @@ describe('flex directive', () => {
});
});

it('should work fxLayout parents', () => {
fixture = componentWithTemplate(`
<div fxLayout="column">
<div fxFlex="30px" fxFlex.gt-sm="50" > </div>
</div>
`);
fixture.detectChanges();
let nodes = queryFor(fixture, "[fxFlex]");

// parent flex-direction found with 'column' with child height stylings
expect(nodes[0].nativeElement).toHaveCssStyle({ 'min-height': '30px' });
expect(nodes[0].nativeElement).not.toHaveCssStyle({ 'min-width': '30px' });
});

it('should not work with non-direct-parent fxLayouts', async(() => {
fixture = componentWithTemplate(`
<div fxLayout="column">
<div class="test">
<div fxFlex="40px" fxFlex.gt-sm="50" > </div>
</div>
</div>
`);
fixture.detectChanges();
let element = queryFor(fixture, "[fxFlex]")[0].nativeElement;
let parent = queryFor(fixture, ".test")[0].nativeElement;

setTimeout(() => {
// The parent flex-direction not found;
// A flex-direction should have been auto-injected to the parent...
// fallback to 'row' and set child width styles accordingly
expect(parent).toHaveCssStyle({ 'flex-direction': 'row' });
expect(element).toHaveCssStyle({ 'min-width': '40px' });
expect(element).not.toHaveCssStyle({ 'min-height': '40px' });
});

}));

it('should work with styled-parent flex directions', () => {
fixture = componentWithTemplate(`
<div fxLayout="row">
<div style="flex-direction:column">
<div fxFlex="60px" > </div>
</div>
</div>
`);
fixture.detectChanges();
let nodes = queryFor(fixture, "[fxFlex]");

// parent flex-direction found with 'column'; set child with height styles
expect(nodes[0].nativeElement).toHaveCssStyle({ 'min-height': '60px' });
});

it('should work with "1 1 auto" values', () => {
fixture = componentWithTemplate(`
<div fxLayout="column">
Expand Down
38 changes: 5 additions & 33 deletions src/lib/flexbox/api/flex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@
SimpleChanges,
SkipSelf,
} from '@angular/core';
import {Subscription} from 'rxjs/Subscription';
import {extendObject} from '../../utils/object-extend';

import {BaseFxDirective} from './base';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';

import {LayoutDirective} from './layout';
import {LayoutWrapDirective} from './layout-wrap';
import {validateBasis} from '../../utils/basis-validator';

Expand All @@ -51,12 +49,6 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
/** The flex-direction of this element's flex container. Defaults to 'row'. */
protected _layout = 'row';

/**
* Subscription to the parent flex container's layout changes.
* Stored so we can unsubscribe when this directive is destroyed.
*/
protected _layoutWatcher: Subscription;

/* tslint:disable */
@Input('fxShrink') set shrink(val) { this._cacheInput("shrink", val); };
@Input('fxGrow') set grow(val) { this._cacheInput("grow", val); };
Expand Down Expand Up @@ -84,31 +76,21 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
constructor(monitor: MediaMonitor,
elRef: ElementRef,
renderer: Renderer,
@Optional() @SkipSelf() protected _container: LayoutDirective,
@Optional() @SkipSelf() protected _wrap: LayoutWrapDirective) {

super(monitor, elRef, renderer);

this._cacheInput("flex", "");
this._cacheInput("shrink", 1);
this._cacheInput("grow", 1);

if (_container) {
// If this flex item is inside of a flex container marked with
// Subscribe to layout immediate parent direction changes
this._layoutWatcher = _container.layout$.subscribe((direction) => {
// `direction` === null if parent container does not have a `fxLayout`
this._onLayoutChange(direction);
});
}
}

/**
* For @Input changes on the current mq activation property, see onMediaQueryChanges()
*/
ngOnChanges(changes: SimpleChanges) {
if (changes['flex'] != null || this._mqActivation) {
this._onLayoutChange();
this._updateStyle();
}
}

Expand All @@ -120,27 +102,17 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
this._listenForMediaQueryChanges('flex', '', (changes: MediaChange) => {
this._updateStyle(changes.value);
});
this._onLayoutChange();
this._updateStyle();
}

ngOnDestroy() {
super.ngOnDestroy();
if (this._layoutWatcher) {
this._layoutWatcher.unsubscribe();
}
}


/**
* Caches the parent container's 'flex-direction' and updates the element's style.
* Used as a handler for layout change events from the parent flex container.
*/
protected _onLayoutChange(direction?: string) {
this._layout = direction || this._layout || "row";
this._updateStyle();
}

protected _updateStyle(value?: string|number) {
this._layout = this._getParentFlowDirection() || "row";

let flexBasis = value || this._queryInput("flex") || '';
if (this._mqActivation) {
flexBasis = this._mqActivation.activatedInput;
Expand All @@ -159,7 +131,7 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges,
shrink: number|string,
basis: string|number|FlexBasisAlias) {
let css, isValue;
let direction = (this._layout === 'column') || (this._layout == 'column-reverse') ?
let direction = (this._layout === 'column') || (this._layout === 'column-reverse') ?
'column' :
'row';

Expand Down
3 changes: 2 additions & 1 deletion src/lib/flexbox/api/layout-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {BaseFxDirective} from './base';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';

import {LAYOUT_VALUES, LayoutDirective} from './layout';
import {LayoutDirective} from './layout';
import {LAYOUT_VALUES} from '../../utils/layout-validator';


/**
Expand Down
4 changes: 2 additions & 2 deletions src/lib/flexbox/api/layout-gap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import {
import {Subscription} from 'rxjs/Subscription';

import {BaseFxDirective} from './base';
import {LayoutDirective} from './layout';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';
import {LayoutDirective, LAYOUT_VALUES} from './layout';

import {LAYOUT_VALUES} from '../../utils/layout-validator';
/**
* 'layout-padding' styling directive
* Defines padding of child elements in a layout container
Expand Down
30 changes: 3 additions & 27 deletions src/lib/flexbox/api/layout-wrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import {Subscription} from 'rxjs/Subscription';
import {extendObject} from '../../utils/object-extend';

import {BaseFxDirective} from './base';
import {LayoutDirective} from './layout';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';
import {LayoutDirective, LAYOUT_VALUES} from './layout';

import {validateWrapValue, LAYOUT_VALUES} from '../../utils/layout-validator';
/**
* @deprecated
* This functionality is now part of the `fxLayout` API
Expand Down Expand Up @@ -118,7 +118,7 @@ export class LayoutWrapDirective extends BaseFxDirective implements OnInit, OnCh
if (this._mqActivation) {
value = this._mqActivation.activatedInput;
}
value = this._validateValue(value);
value = validateWrapValue(value);

this._applyStyleToElement(this._buildCSS(value));
}
Expand All @@ -133,28 +133,4 @@ export class LayoutWrapDirective extends BaseFxDirective implements OnInit, OnCh
'flex-direction' : this._layout || 'row'
});
}

/**
* Convert layout-wrap="<value>" to expected flex-wrap style
*/
protected _validateValue(value) {
switch (value.toLowerCase()) {
case 'reverse':
case 'wrap-reverse':
value = 'wrap-reverse';
break;

case 'no':
case 'none':
case 'nowrap':
value = 'nowrap';
break;

// All other values fallback to "wrap"
default:
value = 'wrap';
break;
}
return value;
}
}
70 changes: 6 additions & 64 deletions src/lib/flexbox/api/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ import {Observable} from 'rxjs/Observable';
import {BaseFxDirective} from './base';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';

export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse'];

import {buildLayoutCSS} from '../../utils/layout-validator';
/**
* 'layout' flexbox styling directive
* Defines the positioning flow direction for the child elements: row or column
Expand Down Expand Up @@ -117,73 +115,17 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange
if (this._mqActivation) {
value = this._mqActivation.activatedInput;
}
let [direction, wrap] = this._validateValue(value);

// Update styles and announce to subscribers the *new* direction
this._applyStyleToElement(this._buildCSS(direction, wrap));
this._announcer.next(direction);
let css = buildLayoutCSS(value);

this._applyStyleToElement(css);
this._announcer.next(css['flex-direction']);
}


/**
* Build the CSS that should be assigned to the element instance
* BUG:
*
* 1) min-height on a column flex container won’t apply to its flex item children in IE 10-11.
* Use height instead if possible; height : <xxx>vh;
*
* @todo - update all child containers to have "box-sizing: border-box"
* This way any padding or border specified on the child elements are
* laid out and drawn inside that element's specified width and height.
*
*/
protected _buildCSS(direction, wrap = null) {
return {
'display': 'flex',
'box-sizing': 'border-box',
'flex-direction': direction,
'flex-wrap': !!wrap ? wrap : null
};
}

/**
* Validate the value to be one of the acceptable value options
* Use default fallback of "row"
*/
protected _validateValue(value) {
value = value ? value.toLowerCase() : '';
let [ direction, wrap ] = value.split(" ");
if (!LAYOUT_VALUES.find(x => x === direction)) {
direction = LAYOUT_VALUES[0];
}
return [direction, this._validateWrapValue(wrap)];

}

/**
* Convert layout-wrap="<value>" to expected flex-wrap style
*/
protected _validateWrapValue(value) {
if (!!value) {
switch (value.toLowerCase()) {
case 'reverse':
case 'wrap-reverse':
case 'reverse-wrap':
value = 'wrap-reverse';
break;

case 'no':
case 'none':
case 'nowrap':
value = 'nowrap';
break;

// All other values fallback to "wrap"
default:
value = 'wrap';
break;
}
}
return value;
}

}
8 changes: 8 additions & 0 deletions src/lib/utils/basis-validator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* The flex API permits 3 or 1 parts of the value:
* - `flex-grow flex-shrink flex-basis`, or
* - `flex-basis`
Expand Down
10 changes: 9 additions & 1 deletion src/lib/utils/breakpoint-tools.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import {BreakPoint} from '../media-query/breakpoints/break-point';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {BreakPoint} from '../media-query/breakpoints/break-point';
import {extendObject} from './object-extend';

const ALIAS_DELIMITERS = /(\.|-|_)/g;
Expand Down
1 change: 1 addition & 0 deletions src/lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export * from './add-alias';
export * from './auto-prefixer';
export * from './basis-validator';
export * from './layout-validator';
export * from './breakpoint-tools';
export * from './object-extend';
export * from './style-transforms';
Loading

0 comments on commit 2ea5dfd

Please sign in to comment.