diff --git a/src/lib/flexbox/api/base.ts b/src/lib/flexbox/api/base.ts index aba765f38..faca463d9 100644 --- a/src/lib/flexbox/api/base.ts +++ b/src/lib/flexbox/api/base.ts @@ -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'; @@ -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. */ diff --git a/src/lib/flexbox/api/flex.spec.ts b/src/lib/flexbox/api/flex.spec.ts index e0bb98088..aeb14c00f 100644 --- a/src/lib/flexbox/api/flex.spec.ts +++ b/src/lib/flexbox/api/flex.spec.ts @@ -103,6 +103,58 @@ describe('flex directive', () => { }); }); + it('should work fxLayout parents', () => { + fixture = componentWithTemplate(` +
+
+
+ `); + 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(` +
+
+
+
+
+ `); + 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(` +
+
+
+
+
+ `); + 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(`
diff --git a/src/lib/flexbox/api/flex.ts b/src/lib/flexbox/api/flex.ts index a25135e7e..ab7bab5db 100644 --- a/src/lib/flexbox/api/flex.ts +++ b/src/lib/flexbox/api/flex.ts @@ -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'; @@ -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); }; @@ -84,7 +76,6 @@ 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); @@ -92,15 +83,6 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, 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); - }); - } } /** @@ -108,7 +90,7 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, */ ngOnChanges(changes: SimpleChanges) { if (changes['flex'] != null || this._mqActivation) { - this._onLayoutChange(); + this._updateStyle(); } } @@ -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; @@ -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'; diff --git a/src/lib/flexbox/api/layout-align.ts b/src/lib/flexbox/api/layout-align.ts index 828c5eac5..25b067632 100644 --- a/src/lib/flexbox/api/layout-align.ts +++ b/src/lib/flexbox/api/layout-align.ts @@ -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'; /** diff --git a/src/lib/flexbox/api/layout-gap.ts b/src/lib/flexbox/api/layout-gap.ts index e1f9213ef..ea09302b8 100644 --- a/src/lib/flexbox/api/layout-gap.ts +++ b/src/lib/flexbox/api/layout-gap.ts @@ -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 diff --git a/src/lib/flexbox/api/layout-wrap.ts b/src/lib/flexbox/api/layout-wrap.ts index a1330b50d..bfd3ce5d1 100644 --- a/src/lib/flexbox/api/layout-wrap.ts +++ b/src/lib/flexbox/api/layout-wrap.ts @@ -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 @@ -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)); } @@ -133,28 +133,4 @@ export class LayoutWrapDirective extends BaseFxDirective implements OnInit, OnCh 'flex-direction' : this._layout || 'row' }); } - - /** - * Convert layout-wrap="" 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; - } } diff --git a/src/lib/flexbox/api/layout.ts b/src/lib/flexbox/api/layout.ts index 0a90863eb..5fe25940d 100644 --- a/src/lib/flexbox/api/layout.ts +++ b/src/lib/flexbox/api/layout.ts @@ -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 @@ -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 : 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="" 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; - } + } diff --git a/src/lib/utils/basis-validator.ts b/src/lib/utils/basis-validator.ts index 24c30caa5..5a15d9911 100644 --- a/src/lib/utils/basis-validator.ts +++ b/src/lib/utils/basis-validator.ts @@ -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` diff --git a/src/lib/utils/breakpoint-tools.ts b/src/lib/utils/breakpoint-tools.ts index 008812b88..a6c4af0dd 100644 --- a/src/lib/utils/breakpoint-tools.ts +++ b/src/lib/utils/breakpoint-tools.ts @@ -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; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index a6b236653..003f87346 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -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'; diff --git a/src/lib/utils/layout-validator.ts b/src/lib/utils/layout-validator.ts new file mode 100644 index 000000000..61ad95bbb --- /dev/null +++ b/src/lib/utils/layout-validator.ts @@ -0,0 +1,76 @@ +/** + * @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 + */ + +export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse']; + +/** + * Validate the direction|"direction wrap" value and then update the host's inline flexbox styles + */ +export function buildLayoutCSS(value: string) { + let [direction, wrap] = validateValue(value); + return buildCSS(direction, wrap); + } + +/** + * Convert layout-wrap="" to expected flex-wrap style + */ +export function 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; +} + + +/** + * Validate the value to be one of the acceptable value options + * Use default fallback of "row" + */ +function validateValue(value: string) { + value = value ? value.toLowerCase() : ''; + let [direction, wrap] = value.split(" "); + if (!LAYOUT_VALUES.find(x => x === direction)) { + direction = LAYOUT_VALUES[0]; + } + return [direction, validateWrapValue(wrap)]; +} + +/** + * 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 : vh; + * + * This way any padding or border specified on the child elements are + * laid out and drawn inside that element's specified width and height. + */ +function buildCSS(direction, wrap = null) { + return { + 'display': 'flex', + 'box-sizing': 'border-box', + 'flex-direction': direction, + 'flex-wrap': !!wrap ? wrap : null + }; +}