diff --git a/src/lib/flexbox/api/base.ts b/src/lib/flexbox/api/base.ts index 692640742..976182efe 100644 --- a/src/lib/flexbox/api/base.ts +++ b/src/lib/flexbox/api/base.ts @@ -1,6 +1,9 @@ -import {ElementRef, Renderer} from '@angular/core'; +import {ElementRef, Renderer, OnDestroy} from '@angular/core'; import {applyCssPrefixes} from '../../utils/auto-prefixer'; + +import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; import {MediaMonitor} from '../../media-query/media-monitor'; +import {MediaQuerySubscriber} from '../../media-query/media-change'; /** * Definition of a css style. Either a property name (e.g. "flex-basis") or an object @@ -9,16 +12,62 @@ import {MediaMonitor} from '../../media-query/media-monitor'; export type StyleDefinition = string|{[property: string]: string|number}; /** Abstract base class for the Layout API styling directives. */ -export abstract class BaseFxDirective { - constructor(private _mediaMonitor : MediaMonitor, private _elementRef: ElementRef, private _renderer: Renderer) {} +export abstract class BaseFxDirective implements OnDestroy { + /** + * MediaQuery Activation Tracker + */ + protected _mqActivation: ResponsiveActivation; + + /** + * Dictionary of input keys with associated values + */ + protected _inputMap: Map; + + /** + * + */ + constructor(private _mediaMonitor: MediaMonitor, private _elementRef: ElementRef, private _renderer: Renderer) { + this._inputMap = new Map(); + } + + // ********************************************* + // Accessor Methods + // ********************************************* /** * Accessor used by the ResponsiveActivation to subscribe to mediaQuery change notifications */ - get mediaMonitor() : MediaMonitor { + get mediaMonitor(): MediaMonitor { return this._mediaMonitor; } - /** Applies styles given via string pair or object map to the directive element. */ + + /** + * Access the current value (if any) of the @Input property. + */ + protected _queryInput(key) { + return this._inputMap.get(key); + } + + + // ********************************************* + // Lifecycle Methods + // ********************************************* + + /** + * + */ + ngOnDestroy() { + this._mqActivation.destroy(); + this._mediaMonitor = null; + } + + // ********************************************* + // Protected Methods + // ********************************************* + + /** + * Applies styles given via string pair or object map to the directive element. + */ protected _applyStyleToElement(style: StyleDefinition, value?: string|number) { let styles = {}; let element = this._elementRef.nativeElement; @@ -35,4 +84,29 @@ export abstract class BaseFxDirective { this._renderer.setElementStyle(element, key, styles[key]); } } + + /** + * Save the property value; which may be a complex object. + * Complex objects support property chains + */ + protected _cacheInput(key, source) { + if (typeof source === 'object') { + for (let prop in source) { + this._inputMap.set(prop, source[prop]); + } + } else { + this._inputMap.set(key, source); + } + } + + /** + * Build a ResponsiveActivation object used to manage subscriptions to mediaChange notifications + * and intelligent lookup of the directive's property value that corresponds to that mediaQuery + * (or closest match). + */ + protected _listenForMediaQueryChanges(key: string, defaultVal: any, + onMediaQueryChange: MediaQuerySubscriber): ResponsiveActivation { + let keyOptions = new KeyOptions(key, defaultVal, this._inputMap); + return this._mqActivation = new ResponsiveActivation(this, keyOptions, onMediaQueryChange); + } } diff --git a/src/lib/flexbox/api/flex-align.ts b/src/lib/flexbox/api/flex-align.ts index fce4cf329..cd140d89b 100644 --- a/src/lib/flexbox/api/flex-align.ts +++ b/src/lib/flexbox/api/flex-align.ts @@ -12,36 +12,26 @@ import { import {BaseFxDirective} from './base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** * 'flex-align' flexbox styling directive * Allows element-specific overrides for cross-axis alignments in a layout container * @see https://css-tricks.com/almanac/properties/a/align-self/ */ -@Directive({selector: '[fx-flex-align]'}) +@Directive({selector: addResponsiveAliases('fx-flex-align')}) export class FlexAlignDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; - - @Input('fx-flex-align') align: string = 'stretch'; // default - - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-flex-align.xs') alignXs; - @Input('fx-flex-align.gt-xs') alignGtXs; - @Input('fx-flex-align.sm') alignSm; - @Input('fx-flex-align.gt-sm') alignGtSm; - @Input('fx-flex-align.md') alignMd; - @Input('fx-flex-align.gt-md') alignGtMd; - @Input('fx-flex-align.lg') alignLg; - @Input('fx-flex-align.gt-lg') alignGtLg; - @Input('fx-flex-align.xl') alignXl; + @Input('fx-flex-align') set align(val) { this._cacheInput('align', val); } + @Input('fx-flex-align.xs') set alignXs(val) { this._cacheInput('alignXs', val); } + @Input('fx-flex-align.gt-xs') set alignGtXs(val) { this._cacheInput('alignGtXs', val); }; + @Input('fx-flex-align.sm') set alignSm(val) { this._cacheInput('alignSm', val); }; + @Input('fx-flex-align.gt-sm') set alignGtSm(val) { this._cacheInput('alignGtSm', val); }; + @Input('fx-flex-align.md') set alignMd(val) { this._cacheInput('alignMd', val); }; + @Input('fx-flex-align.gt-md') set alignGtMd(val) { this._cacheInput('alignGtMd', val); }; + @Input('fx-flex-align.lg') set alignLg(val) { this._cacheInput('alignLg', val); }; + @Input('fx-flex-align.gt-lg') set alignGtLg(val) { this._cacheInput('alignGtLg', val); }; + @Input('fx-flex-align.xl') set alignXl(val) { this._cacheInput('alignXl', val); }; constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) { super(monitor, elRef, renderer); @@ -66,23 +56,18 @@ export class FlexAlignDirective extends BaseFxDirective implements OnInit, OnCha * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('align', 'stretch'); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('align', 'stretch', (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); this._updateWithValue(); } - ngOnDestroy() { - this._mqActivation.destroy(); - } - // ********************************************* // Protected methods // ********************************************* _updateWithValue(value?: string|number) { - value = value || this.align || 'stretch'; + value = value || this._queryInput("align") || 'stretch'; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/api/flex-fill.ts b/src/lib/flexbox/api/flex-fill.ts index f3dd774f9..888f5b5b5 100644 --- a/src/lib/flexbox/api/flex-fill.ts +++ b/src/lib/flexbox/api/flex-fill.ts @@ -2,6 +2,7 @@ import {Directive, ElementRef, Renderer} from '@angular/core'; import {MediaMonitor} from '../../media-query/media-monitor'; import {BaseFxDirective} from './base'; +import {addResponsiveAliases} from '../../utils/add-alias'; const FLEX_FILL_CSS = { 'margin': 0, @@ -12,12 +13,12 @@ const FLEX_FILL_CSS = { }; /** - * 'fx-flex-fill' flexbox styling directive + * 'fx-fill' flexbox styling directive * Maximizes width and height of element in a layout container * - * NOTE: [fx-flexFill] is NOT responsive fx-flex + * NOTE: fx-fill is NOT responsive API!! */ -@Directive({selector: '[fx-flex-fill]'}) +@Directive({selector: "[fx-fill], " + addResponsiveAliases('fx-flex-fill')}) export class FlexFillDirective extends BaseFxDirective { constructor(monitor : MediaMonitor, public elRef: ElementRef, public renderer: Renderer) { super(monitor, elRef, renderer); diff --git a/src/lib/flexbox/api/flex-offset.ts b/src/lib/flexbox/api/flex-offset.ts index d826573aa..f51583c1f 100644 --- a/src/lib/flexbox/api/flex-offset.ts +++ b/src/lib/flexbox/api/flex-offset.ts @@ -13,35 +13,26 @@ import { import {BaseFxDirective} from './base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** * 'flex-offset' flexbox styling directive * Configures the 'margin-left' of the element in a layout container */ -@Directive({selector: '[fx-flex-offset]'}) +@Directive({selector: addResponsiveAliases('fx-flex-offset')}) export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; - - @Input('fx-flex-offset') offset: string|number; - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-flex-offset.xs') offsetXs: string|number; - @Input('fx-flex-offset.gt-xs') offsetGtXs: string|number; - @Input('fx-flex-offset.sm') offsetSm: string|number; - @Input('fx-flex-offset.gt-sm') offsetGtSm: string|number; - @Input('fx-flex-offset.md') offsetMd: string|number; - @Input('fx-flex-offset.gt-md') offsetGtMd: string|number; - @Input('fx-flex-offset.lg') offsetLg: string|number; - @Input('fx-flex-offset.gt-lg') offsetGtLg: string|number; - @Input('fx-flex-offset.xl') offsetXl: string|number; + @Input('fx-flex-offset') set offset(val) { this._cacheInput('offset', val); } + @Input('fx-flex-offset.xs') set offsetXs(val) { this._cacheInput('offsetXs', val); } + @Input('fx-flex-offset.gt-xs') set offsetGtXs(val) { this._cacheInput('offsetGtXs', val); }; + @Input('fx-flex-offset.sm') set offsetSm(val) { this._cacheInput('offsetSm', val); }; + @Input('fx-flex-offset.gt-sm') set offsetGtSm(val) { this._cacheInput('offsetGtSm', val); }; + @Input('fx-flex-offset.md') set offsetMd(val) { this._cacheInput('offsetMd', val); }; + @Input('fx-flex-offset.gt-md') set offsetGtMd(val) { this._cacheInput('offsetGtMd', val); }; + @Input('fx-flex-offset.lg') set offsetLg(val) { this._cacheInput('offsetLg', val); }; + @Input('fx-flex-offset.gt-lg') set offsetGtLg(val) { this._cacheInput('offsetGtLg', val); }; + @Input('fx-flex-offset.xl') set offsetXl(val) { this._cacheInput('offsetXl', val); }; constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) { super(monitor, elRef, renderer); @@ -65,23 +56,18 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('offset', 0 ); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('offset', 0 , (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); } - ngOnDestroy() { - this._mqActivation.destroy(); - } - // ********************************************* // Protected methods // ********************************************* _updateWithValue(value?: string|number) { - value = value || this.offset || 0; + value = value || this._queryInput("offset") || 0; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/api/flex-order.ts b/src/lib/flexbox/api/flex-order.ts index 10ee71f45..cc2d8ef4a 100644 --- a/src/lib/flexbox/api/flex-order.ts +++ b/src/lib/flexbox/api/flex-order.ts @@ -12,35 +12,26 @@ import { import {BaseFxDirective} from './base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** * 'flex-order' flexbox styling directive * Configures the positional ordering of the element in a sorted layout container * @see https://css-tricks.com/almanac/properties/o/order/ */ -@Directive({selector: '[fx-flex-order]'}) +@Directive({selector: addResponsiveAliases('fx-flex-order')}) export class FlexOrderDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; - - @Input('fx-flex-order') order; - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-flex-order.xs') orderXs; - @Input('fx-flex-order.gt-xs') orderGtXs; - @Input('fx-flex-order.sm') orderSm; - @Input('fx-flex-order.gt-sm') orderGtSm; - @Input('fx-flex-order.md') orderMd; - @Input('fx-flex-order.gt-md') orderGtMd; - @Input('fx-flex-order.lg') orderLg; - @Input('fx-flex-order.gt-lg') orderGtLg; - @Input('fx-flex-order.xl') orderXl; + @Input('fx-flex-order') set order(val) { this._cacheInput('order', val); } + @Input('fx-flex-order.xs') set orderXs(val) { this._cacheInput('orderXs', val); } + @Input('fx-flex-order.gt-xs') set orderGtXs(val) { this._cacheInput('orderGtXs', val); }; + @Input('fx-flex-order.sm') set orderSm(val) { this._cacheInput('orderSm', val); }; + @Input('fx-flex-order.gt-sm') set orderGtSm(val) { this._cacheInput('orderGtSm', val); }; + @Input('fx-flex-order.md') set orderMd(val) { this._cacheInput('orderMd', val); }; + @Input('fx-flex-order.gt-md') set orderGtMd(val) { this._cacheInput('orderGtMd', val); }; + @Input('fx-flex-order.lg') set orderLg(val) { this._cacheInput('orderLg', val); }; + @Input('fx-flex-order.gt-lg') set orderGtLg(val) { this._cacheInput('orderGtLg', val); }; + @Input('fx-flex-order.xl') set orderXl(val) { this._cacheInput('orderXl', val); }; constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) { super(monitor, elRef, renderer); @@ -64,23 +55,18 @@ export class FlexOrderDirective extends BaseFxDirective implements OnInit, OnCha * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('order', '1'); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('order', '1', (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); this._updateWithValue(); } - ngOnDestroy() { - this._mqActivation.destroy(); - } - // ********************************************* // Protected methods // ********************************************* _updateWithValue(value?: string) { - value = value || this.order || '1'; + value = value || this._queryInput("order") || '1'; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/api/flex.ts b/src/lib/flexbox/api/flex.ts index 34240d36b..682685d2a 100644 --- a/src/lib/flexbox/api/flex.ts +++ b/src/lib/flexbox/api/flex.ts @@ -20,6 +20,7 @@ import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activat import {LayoutDirective} from './layout'; import {LayoutWrapDirective} from './layout-wrap'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** Built-in aliases for different flex-basis values. */ @@ -32,14 +33,8 @@ export type FlexBasisAlias = 'grow' | 'initial' | 'auto' | 'none' | 'nogrow' | ' * * @see https://css-tricks.com/snippets/css/a-guide-to-flexbox/ */ -@Directive({ - selector: '[fx-flex]', -}) -export class FlexDirective extends BaseFxDirective - implements OnInit, OnChanges, OnDestroy { - - /** MediaQuery Activation Tracker */ - private _mqActivation: ResponsiveActivation; +@Directive({ selector: addResponsiveAliases('fx-flex') }) +export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { /** The flex-direction of this element's flex container. Defaults to 'row'. */ private _layout = 'row'; @@ -50,20 +45,19 @@ export class FlexDirective extends BaseFxDirective */ private _layoutWatcher: Subscription; - @Input('fx-flex') flex: string = ''; - @Input('fx-shrink') shrink: number = 1; - @Input('fx-grow') grow: number = 1; - - // Optional input variations to support mediaQuery triggers - @Input('fx-flex.xs') flexXs; - @Input('fx-flex.gt-xs') flexGtXs; - @Input('fx-flex.sm') flexSm; - @Input('fx-flex.gt-sm') flexGtSm; - @Input('fx-flex.md') flexMd; - @Input('fx-flex.gt-md') flexGtMd; - @Input('fx-flex.lg') flexLg; - @Input('fx-flex.gt-lg') flexGtLg; - @Input('fx-flex.xl') flexXl; + @Input('fx-flex') set flex(val) { this._cacheInput("flex", val); } + @Input('fx-shrink') set shrink(val) { this._cacheInput("shrink", val); } + @Input('fx-grow') set grow(val) { this._cacheInput("grow", val); } + + @Input('fx-flex.xs') set flexXs(val) { this._cacheInput('flexXs', val); } + @Input('fx-flex.gt-xs') set flexGtXs(val) { this._cacheInput('flexGtXs', val); }; + @Input('fx-flex.sm') set flexSm(val) { this._cacheInput('flexSm', val); }; + @Input('fx-flex.gt-sm') set flexGtSm(val) { this._cacheInput('flexGtSm', val); }; + @Input('fx-flex.md') set flexMd(val) { this._cacheInput('flexMd', val); }; + @Input('fx-flex.gt-md') set flexGtMd(val) { this._cacheInput('flexGtMd', val); }; + @Input('fx-flex.lg') set flexLg(val) { this._cacheInput('flexLg', val); }; + @Input('fx-flex.gt-lg') set flexGtLg(val) { this._cacheInput('flexGtLg', val); }; + @Input('fx-flex.xl') set flexXl(val) { this._cacheInput('flexXl', val); }; // Explicitly @SkipSelf on LayoutDirective and LayoutWrapDirective because we want the @@ -76,6 +70,11 @@ export class FlexDirective extends BaseFxDirective @Optional() @SkipSelf() private _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 @@ -97,15 +96,14 @@ export class FlexDirective extends BaseFxDirective * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('flex', ''); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('flex', '', (changes: MediaChange) =>{ this._updateStyle(changes.value); }); this._onLayoutChange(); } ngOnDestroy() { - this._mqActivation.destroy(); + super.ngOnDestroy(); if (this._layoutWatcher) { this._layoutWatcher.unsubscribe(); } @@ -122,12 +120,20 @@ export class FlexDirective extends BaseFxDirective } _updateStyle(value?: string) { - let flexBasis = value || this.flex || ''; + let flexBasis = value || this._queryInput("flex") || ''; if (this._mqActivation) { flexBasis = this._mqActivation.activatedInput; } - this._applyStyleToElement(this._validateValue(this.grow, this.shrink, flexBasis)); + this._applyStyleToElement(this._validateValue.apply(this, this._parseFlexParts(flexBasis) )); + } + + /** + * If the used the short-form `fx-flex="1 0 37%"`, then parse the parts + */ + _parseFlexParts(basis:string) { + let matches = basis.split(" "); + return (matches.length !== 3) ? [ this._queryInput("grow"), this._queryInput("shrink"), basis ] : matches; } /** @@ -183,10 +189,12 @@ export class FlexDirective extends BaseFxDirective default: let isPercent = String(basis).indexOf('%') > -1; - let isPx = String(basis).indexOf('px') > -1; + let isValue = String(basis).indexOf('px') > -1 || + String(basis).indexOf('vw') > -1 || + String(basis).indexOf('vh') > -1; // Defaults to percentage sizing unless `px` is explicitly set - if (!isPx && !isPercent && !isNaN(basis as any)) + if (!isValue && !isPercent && !isNaN(basis as any)) basis = basis + '%'; if (basis === '0px') basis = '0%'; @@ -195,7 +203,7 @@ export class FlexDirective extends BaseFxDirective // @see https://github.com/philipwalton/flexbugs#11-min-and-max-size-declarations-are-ignored-when-wrappifl-flex-items css = extendObject(clearStyles, { - 'flex': `${grow} ${shrink} ${(isPx || this._wrap) ? basis : '100%'}`, // fix issue #5345 + 'flex': `${grow} ${shrink} ${(isValue || this._wrap) ? basis : '100%'}`, // fix issue #5345 }); break; } diff --git a/src/lib/flexbox/api/hide.ts b/src/lib/flexbox/api/hide.ts index 8e3894820..bf3eee0d5 100644 --- a/src/lib/flexbox/api/hide.ts +++ b/src/lib/flexbox/api/hide.ts @@ -20,47 +20,35 @@ import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activat import {ShowDirective} from "./show"; import {LayoutDirective} from './layout'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** * 'show' Layout API directive * */ -@Directive({selector: '[fx-hide]' }) +@Directive({selector: addResponsiveAliases('fx-hide') }) export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { /** * Original dom Elements CSS display style */ private _display = 'flex'; - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; - /** * Subscription to the parent flex container's layout changes. * Stored so we can unsubscribe when this directive is destroyed. */ private _layoutWatcher : Subscription; - /** - * Default layout property with default visible === true - */ - @Input('fx-hide') hide = true; - - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-hide.xs') hideXs; - @Input('fx-hide.gt-xs') hideGtXs; - @Input('fx-hide.sm') hideSm; - @Input('fx-hide.gt-sm') hideGtSm; - @Input('fx-hide.md') hideMd; - @Input('fx-hide.gt-md') hideGtMd; - @Input('fx-hide.lg') hideLg; - @Input('fx-hide.gt-lg') hideGtLg; - @Input('fx-hide.xl') hideXl; + @Input('fx-hide') set hide(val) { this._cacheInput("hide", val); } + @Input('fx-hide.xs') set hideXs(val) { this._cacheInput('hideXs', val); } + @Input('fx-hide.gt-xs') set hideGtXs(val) { this._cacheInput('hideGtXs', val); }; + @Input('fx-hide.sm') set hideSm(val) { this._cacheInput('hideSm', val); }; + @Input('fx-hide.gt-sm') set hideGtSm(val) { this._cacheInput('hideGtSm', val); }; + @Input('fx-hide.md') set hideMd(val) { this._cacheInput('hideMd', val); }; + @Input('fx-hide.gt-md') set hideGtMd(val) { this._cacheInput('hideGtMd', val); }; + @Input('fx-hide.lg') set hideLg(val) { this._cacheInput('hideLg', val); }; + @Input('fx-hide.gt-lg') set hideGtLg(val) { this._cacheInput('hideGtLg', val); }; + @Input('fx-hide.xl') set hideXl(val) { this._cacheInput('hideXl', val); }; /** * @@ -109,8 +97,7 @@ export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('hide', true); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('hide', true, (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); this._updateWithValue(); @@ -118,7 +105,7 @@ export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, ngOnDestroy() { - this._mqActivation.destroy(); + super.ngOnDestroy(); if (this._layoutWatcher) { this._layoutWatcher.unsubscribe(); } @@ -132,7 +119,7 @@ export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, * Validate the visibility value and then update the host's inline display style */ _updateWithValue(value?: string|number|boolean) { - value = value || this.hide || true; + value = value || this._queryInput("hide") || true; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/api/layout-align.ts b/src/lib/flexbox/api/layout-align.ts index d4ac928ab..47b740217 100644 --- a/src/lib/flexbox/api/layout-align.ts +++ b/src/lib/flexbox/api/layout-align.ts @@ -14,9 +14,9 @@ import {Subscription} from 'rxjs/Subscription'; import {BaseFxDirective} from './base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; import {LAYOUT_VALUES, LayoutDirective} from './layout'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** @@ -28,31 +28,23 @@ import {LAYOUT_VALUES, LayoutDirective} from './layout'; * @see https://css-tricks.com/almanac/properties/a/align-items/ * @see https://css-tricks.com/almanac/properties/a/align-content/ */ -@Directive({selector: '[fx-layout-align]'}) +@Directive({selector: addResponsiveAliases('fx-layout-align')}) export class LayoutAlignDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; private _layout = 'row'; // default flex-direction private _layoutWatcher: Subscription; - @Input('fx-layout-align') align: string = 'start stretch'; - - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* + @Input('fx-layout-align') set align(val) { this._cacheInput('align', val); } + @Input('fx-layout-align.xs') set alignXs(val) { this._cacheInput('alignXs', val); } + @Input('fx-layout-align.gt-xs') set alignGtXs(val) { this._cacheInput('alignGtXs', val); }; + @Input('fx-layout-align.sm') set alignSm(val) { this._cacheInput('alignSm', val); }; + @Input('fx-layout-align.gt-sm') set alignGtSm(val) { this._cacheInput('alignGtSm', val); }; + @Input('fx-layout-align.md') set alignMd(val) { this._cacheInput('alignMd', val); }; + @Input('fx-layout-align.gt-md') set alignGtMd(val) { this._cacheInput('alignGtMd', val); }; + @Input('fx-layout-align.lg') set alignLg(val) { this._cacheInput('alignLg', val); }; + @Input('fx-layout-align.gt-lg') set alignGtLg(val) { this._cacheInput('alignGtLg', val); }; + @Input('fx-layout-align.xl') set alignXl(val) { this._cacheInput('alignXl', val); }; - @Input('fx-layout-align.xs') alignXs; - @Input('fx-layout-align.gt-xs') alignGtXs; - @Input('fx-layout-align.sm') alignSm; - @Input('fx-layout-align.gt-sm') alignGtSm; - @Input('fx-layout-align.md') alignMd; - @Input('fx-layout-align.gt-md') alignGtMd; - @Input('fx-layout-align.lg') alignLg; - @Input('fx-layout-align.gt-lg') alignGtLg; - @Input('fx-layout-align.xl') alignXl; constructor( monitor : MediaMonitor, @@ -81,15 +73,14 @@ export class LayoutAlignDirective extends BaseFxDirective implements OnInit, OnC * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('align', 'start stretch'); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('align', 'start stretch', (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); this._updateWithValue(); } ngOnDestroy() { - this._mqActivation.destroy(); + super.ngOnDestroy(); if ( this._layoutWatcher ) { this._layoutWatcher.unsubscribe(); } @@ -103,7 +94,7 @@ export class LayoutAlignDirective extends BaseFxDirective implements OnInit, OnC * */ _updateWithValue(value?: string) { - value = value || this.align || 'start stretch'; + value = value || this._queryInput("align") || 'start stretch'; if (this._mqActivation) { value = this._mqActivation.activatedInput; } @@ -119,7 +110,7 @@ export class LayoutAlignDirective extends BaseFxDirective implements OnInit, OnC if (!LAYOUT_VALUES.find(x => x === this._layout)) this._layout = 'row'; - let value = this.align || 'start stretch'; + let value = this._queryInput("align") || 'start stretch'; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/api/layout-wrap.ts b/src/lib/flexbox/api/layout-wrap.ts index 9722239a9..574a66866 100644 --- a/src/lib/flexbox/api/layout-wrap.ts +++ b/src/lib/flexbox/api/layout-wrap.ts @@ -12,7 +12,7 @@ import { import {BaseFxDirective} from './base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; +import {addResponsiveAliases} from '../../utils/add-alias'; /** * 'layout-wrap' flexbox styling directive @@ -20,28 +20,19 @@ import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activat * Optional values: reverse, wrap-reverse, none, nowrap, wrap (default)] * @see https://css-tricks.com/almanac/properties/f/flex-wrap/ */ -@Directive({selector: '[fx-layout-wrap]'}) +@Directive({selector: addResponsiveAliases('fx-layout-wrap')}) export class LayoutWrapDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; - - @Input('fx-layout-wrap') wrap: string = 'wrap'; - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-layout-wrap.xs') wrapXs; - @Input('fx-layout-wrap.gt-xs') wrapGtXs; - @Input('fx-layout-wrap.sm') wrapSm; - @Input('fx-layout-wrap.gt-sm') wrapGtSm; - @Input('fx-layout-wrap.md') wrapMd; - @Input('fx-layout-wrap.gt-md') wrapGtMd; - @Input('fx-layout-wrap.lg') wrapLg; - @Input('fx-layout-wrap.gt-lg') wrapGtLg; - @Input('fx-layout-wrap.xl') wrapXl; + @Input('fx-layout-wrap') set wrap(val) { this._cacheInput("wrap", val); } + @Input('fx-layout-wrap.xs') set wrapXs(val) { this._cacheInput('wrapXs', val); } + @Input('fx-layout-wrap.gt-xs') set wrapGtXs(val) { this._cacheInput('wrapGtXs', val); }; + @Input('fx-layout-wrap.sm') set wrapSm(val) { this._cacheInput('wrapSm', val); }; + @Input('fx-layout-wrap.gt-sm') set wrapGtSm(val) { this._cacheInput('wrapGtSm', val); }; + @Input('fx-layout-wrap.md') set wrapMd(val) { this._cacheInput('wrapMd', val); }; + @Input('fx-layout-wrap.gt-md') set wrapGtMd(val) { this._cacheInput('wrapGtMd', val); }; + @Input('fx-layout-wrap.lg') set wrapLg(val) { this._cacheInput('wrapLg', val); }; + @Input('fx-layout-wrap.gt-lg') set wrapGtLg(val) { this._cacheInput('wrapGtLg', val); }; + @Input('fx-layout-wrap.xl') set wrapXl(val) { this._cacheInput('wrapXl', val); }; constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) { super(monitor, elRef, renderer) @@ -62,23 +53,19 @@ export class LayoutWrapDirective extends BaseFxDirective implements OnInit, OnCh * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('wrap', 'wrap'); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('wrap', 'wrap', (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); this._updateWithValue(); } - ngOnDestroy() { - this._mqActivation.destroy(); - } // ********************************************* // Protected methods // ********************************************* _updateWithValue(value?: string) { - value = value || this.wrap || 'wrap'; + value = value || this._queryInput("wrap") || 'wrap'; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/api/layout.ts b/src/lib/flexbox/api/layout.ts index af0b0db3a..d9a22092d 100644 --- a/src/lib/flexbox/api/layout.ts +++ b/src/lib/flexbox/api/layout.ts @@ -14,12 +14,10 @@ import {Observable} from 'rxjs/Observable'; import {BaseFxDirective} from './base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; - +import {addResponsiveAliases} from '../../utils/add-alias'; export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse']; - /** * 'layout' flexbox styling directive * Defines the positioning flow direction for the child elements: row or column @@ -27,49 +25,40 @@ export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse']; * @see https://css-tricks.com/almanac/properties/f/flex-direction/ * */ -@Directive({selector: '[fx-layout]'}) +@Directive({selector: addResponsiveAliases('fx-layout')}) export class LayoutDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; /** * Create Observable for nested/child 'flex' directives. This allows * child flex directives to subscribe/listen for flexbox direction changes. */ - private _announcer: BehaviorSubject = new BehaviorSubject(this.layout); + private _announcer: BehaviorSubject; /** * Publish observer to enabled nested, dependent directives to listen * to parent "layout" direction changes */ - public layout$: Observable = this._announcer.asObservable(); + public layout$: Observable; - /** - * Default layout property with default direction value - */ - @Input('fx-layout') layout = 'row'; - - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-layout.xs') layoutXs; - @Input('fx-layout.gt-xs') layoutGtXs; - @Input('fx-layout.sm') layoutSm; - @Input('fx-layout.gt-sm') layoutGtSm; - @Input('fx-layout.md') layoutMd; - @Input('fx-layout.gt-md') layoutGtMd; - @Input('fx-layout.lg') layoutLg; - @Input('fx-layout.gt-lg') layoutGtLg; - @Input('fx-layout.xl') layoutXl; + + @Input('fx-layout') set layout(val) { this._cacheInput("layout", val); } + @Input('fx-layout.xs') set layoutXs(val) { this._cacheInput('layoutXs', val); } + @Input('fx-layout.gt-xs') set layoutGtXs(val) { this._cacheInput('layoutGtXs', val); }; + @Input('fx-layout.sm') set layoutSm(val) { this._cacheInput('layoutSm', val); }; + @Input('fx-layout.gt-sm') set layoutGtSm(val) { this._cacheInput('layoutGtSm', val); }; + @Input('fx-layout.md') set layoutMd(val) { this._cacheInput('layoutMd', val); }; + @Input('fx-layout.gt-md') set layoutGtMd(val) { this._cacheInput('layoutGtMd', val); }; + @Input('fx-layout.lg') set layoutLg(val) { this._cacheInput('layoutLg', val); }; + @Input('fx-layout.gt-lg') set layoutGtLg(val) { this._cacheInput('layoutGtLg', val); }; + @Input('fx-layout.xl') set layoutXl(val) { this._cacheInput('layoutXl', val); }; /** * */ constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) { super(monitor, elRef, renderer); + this._announcer = new BehaviorSubject("row"); + this.layout$ = this._announcer.asObservable(); } // ********************************************* @@ -92,31 +81,21 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('layout', 'row'); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('layout', 'row', (changes: MediaChange) =>{ this._updateWithDirection(changes.value); }); this._updateWithDirection(); } - ngOnDestroy() { - this._mqActivation.destroy(); - } - // ********************************************* // Protected methods // ********************************************* /** * Validate the direction value and then update the host's inline flexbox styles - * - * @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. - * */ _updateWithDirection(direction?: string) { - direction = direction || this.layout || 'row'; + direction = direction || this._queryInput("layout") || 'row'; if (this._mqActivation) { direction = this._mqActivation.activatedInput; } @@ -134,6 +113,11 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange * * 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. + * */ _buildCSS(value) { return {'display': 'flex', 'box-sizing': 'border-box', 'flex-direction': value}; diff --git a/src/lib/flexbox/api/show.ts b/src/lib/flexbox/api/show.ts index 9584a9beb..41a569335 100644 --- a/src/lib/flexbox/api/show.ts +++ b/src/lib/flexbox/api/show.ts @@ -22,6 +22,7 @@ import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activat import {HideDirective} from "./hide"; import {LayoutDirective} from './layout'; +import {addResponsiveAliases} from '../../utils/add-alias'; @@ -31,43 +32,29 @@ const FALSY = ['false', false, 0]; * 'show' Layout API directive * */ -@Directive({selector: '[fx-show]'}) +@Directive({selector: addResponsiveAliases('fx-show')}) export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { /** * Original dom Elements CSS display style */ private _display = 'flex'; - /** - * MediaQuery Activation Tracker - */ - private _mqActivation: ResponsiveActivation; - /** * Subscription to the parent flex container's layout changes. * Stored so we can unsubscribe when this directive is destroyed. */ private _layoutWatcher : Subscription; - /** - * Default layout property with default visible === true - */ - @Input('fx-show') show = true; - - // ******************************************************* - // Optional input variations to support mediaQuery triggers - // ******************************************************* - - @Input('fx-show.xs') showXs; - @Input('fx-show.gt-xs') showGtXs; - @Input('fx-show.sm') showSm; - @Input('fx-show.gt-sm') showGtSm; - @Input('fx-show.md') showMd; - @Input('fx-show.gt-md') showGtMd; - @Input('fx-show.lg') showLg; - @Input('fx-show.gt-lg') showGtLg; - @Input('fx-show.xl') showXl; - + @Input('fx-show') set show(val) { this._cacheInput("show", val); } + @Input('fx-show.xs') set showXs(val) { this._cacheInput('showXs', val); } + @Input('fx-show.gt-xs') set showGtXs(val) { this._cacheInput('showGtXs', val); }; + @Input('fx-show.sm') set showSm(val) { this._cacheInput('showSm', val); }; + @Input('fx-show.gt-sm') set showGtSm(val) { this._cacheInput('showGtSm', val); }; + @Input('fx-show.md') set showMd(val) { this._cacheInput('showMd', val); }; + @Input('fx-show.gt-md') set showGtMd(val) { this._cacheInput('showGtMd', val); }; + @Input('fx-show.lg') set showLg(val) { this._cacheInput('showLg', val); }; + @Input('fx-show.gt-lg') set showGtLg(val) { this._cacheInput('showGtLg', val); }; + @Input('fx-show.xl') set showXl(val) { this._cacheInput('showXl', val); }; /** * */ @@ -79,6 +66,7 @@ export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, protected renderer: Renderer) { super(monitor, elRef, renderer); + if (_layout) { /** * The Layout can set the display:flex (and incorrectly affect the Hide/Show directives. @@ -116,15 +104,14 @@ export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, * mql change events to onMediaQueryChange handlers */ ngOnInit() { - let keyOptions = new KeyOptions('show', true); - this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{ + this._listenForMediaQueryChanges('show', true, (changes: MediaChange) =>{ this._updateWithValue(changes.value); }); this._updateWithValue(); } ngOnDestroy() { - this._mqActivation.destroy(); + super.ngOnDestroy(); if (this._layoutWatcher) { this._layoutWatcher.unsubscribe(); } @@ -136,7 +123,7 @@ export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, /** Validate the visibility value and then update the host's inline display style */ _updateWithValue(value?: string|number|boolean) { - value = value || this.show || true; + value = value || this._queryInput("show") || true; if (this._mqActivation) { value = this._mqActivation.activatedInput; } diff --git a/src/lib/flexbox/responsive/responsive-activation.ts b/src/lib/flexbox/responsive/responsive-activation.ts index 2897245fb..178a53114 100644 --- a/src/lib/flexbox/responsive/responsive-activation.ts +++ b/src/lib/flexbox/responsive/responsive-activation.ts @@ -14,7 +14,10 @@ export interface BreakPointX extends BreakPoint{ baseKey : string; } export class KeyOptions { - constructor(public baseKey : string, public defaultValue : string|number|boolean) { } + constructor( + public baseKey : string, + public defaultValue : string|number|boolean, + public inputKeys:Map) { } } /** @@ -68,7 +71,7 @@ export class ResponsiveActivation { * Get the currently activated @Input value or the fallback default @Input value */ get activatedInput(): any { - return this._directive[this.activatedInputKey] || this._options.defaultValue; + return this._lookupKeyValue(this.activatedInputKey) || this._options.defaultValue; } /** @@ -141,7 +144,7 @@ export class ResponsiveActivation { * to participate in activation processes. */ private _keyInUse(key ):boolean { - return this._directive[key] !== undefined; + return this._lookupKeyValue(key) !== undefined; } /** @@ -184,4 +187,10 @@ export class ResponsiveActivation { return inputKey; } + /** + * Get the value (if any) for the directive instances @Input property (aka key) + */ + private _lookupKeyValue(key) { + return this._options.inputKeys.get(key); + } } diff --git a/src/lib/media-query/providers/break-points-provider.ts b/src/lib/media-query/providers/break-points-provider.ts index e7587985a..1676b3f4f 100644 --- a/src/lib/media-query/providers/break-points-provider.ts +++ b/src/lib/media-query/providers/break-points-provider.ts @@ -1,6 +1,8 @@ import {OpaqueToken} from '@angular/core'; import {BreakPoint} from '../breakpoints/break-point'; +export const RESPONSIVE_ALIASES = ['xs','gt-xs','sm','gt-sm','md','gt-md','lg','gt-lg','xl']; + export const RAW_DEFAULTS: BreakPoint[ ] = [ { alias: 'xs', diff --git a/src/lib/utils/add-alias.ts b/src/lib/utils/add-alias.ts index 3eaf8d323..323dbaf6f 100644 --- a/src/lib/utils/add-alias.ts +++ b/src/lib/utils/add-alias.ts @@ -3,6 +3,25 @@ import {MediaChange} from '../media-query/media-change'; import {BreakPoint} from '../media-query/breakpoints/break-point'; import {extendObject} from './object-extend'; +import {RESPONSIVE_ALIASES} from '../media-query/providers/break-points-provider'; + +/** + * For the selector, build: + * 1) default, non-responsive attribute selector + * 2) list of attribute selectors which contain the responsive aliases as selector + * with property chains + * e.g. ' [fx-layout], [fx-layout.xs], [fx-layout.gt-xs], [fx-layout.sm], ... ' + */ +export function addResponsiveAliases(attr:string, aliases?:string[]):string { + return (aliases || RESPONSIVE_ALIASES).reduce((all, suffix) => { + return `${all},[${attr}.${suffix}]`; + }, `[${attr}]`); +} + +/** + * For the specified MediaChange, make sure it contains the breakpoint alias + * and suffix (if available). + */ export function mergeAlias(dest:MediaChange, source:BreakPoint) { return extendObject( dest, source ? { mqAlias: source.alias,