diff --git a/src/lib/flexbox/api/base.ts b/src/lib/flexbox/api/base.ts index 4c473d43e..3de4f21ee 100644 --- a/src/lib/flexbox/api/base.ts +++ b/src/lib/flexbox/api/base.ts @@ -5,7 +5,7 @@ * 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 {ElementRef, Renderer, OnDestroy} from '@angular/core'; +import {ElementRef, Renderer, OnDestroy} from '@angular/core'; import {applyCssPrefixes} from '../../utils/auto-prefixer'; import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; @@ -20,6 +20,11 @@ export type StyleDefinition = string|{[property: string]: string|number}; /** Abstract base class for the Layout API styling directives. */ export abstract class BaseFxDirective implements OnDestroy { + /** + * Original dom Elements CSS display style + */ + protected _display; + /** * MediaQuery Activation Tracker */ @@ -36,6 +41,7 @@ export abstract class BaseFxDirective implements OnDestroy { constructor(private _mediaMonitor: MediaMonitor, protected _elementRef: ElementRef, private _renderer: Renderer) { + this._display = this._getDisplayStyle() || "flex"; } // ********************************************* @@ -65,6 +71,26 @@ export abstract class BaseFxDirective implements OnDestroy { // Protected Methods // ********************************************* + /** + * Was the directive's default selector used ? + * If not, use the fallback value! + */ + protected _getDefaultVal(key: string, fallbackVal: any): string|boolean { + let val = this._queryInput(key); + let hasDefaultVal = (val !== undefined && val !== null); + return (hasDefaultVal && val !== '') ? val : fallbackVal; + } + + /** + * Quick accessor to the current HTMLElement's `display` style + * Note: this allows use to preserve the original style + * and optional restore it when the mediaQueries deactivate + */ + protected _getDisplayStyle(): string { + let element: HTMLElement = this._elementRef.nativeElement; + return (element.style as any)['display'] || "flex"; + } + /** * Applies styles given via string pair or object map to the directive element. */ @@ -140,7 +166,7 @@ export abstract class BaseFxDirective implements OnDestroy { var array = []; // iterate backwards ensuring that length is an UInt32 - for ( var i = obj.length; i--; ) { + for (var i = obj.length; i--;) { array[i] = obj[i]; } return array; diff --git a/src/lib/flexbox/api/flex-align.ts b/src/lib/flexbox/api/flex-align.ts index 1c712d0fc..08aee0edb 100644 --- a/src/lib/flexbox/api/flex-align.ts +++ b/src/lib/flexbox/api/flex-align.ts @@ -28,7 +28,7 @@ import {MediaMonitor} from '../../media-query/media-monitor'; @Directive({ selector: ` [fxFlexAlign], - [fxFlexAlign.xs] + [fxFlexAlign.xs], [fxFlexAlign.gt-xs], [fxFlexAlign.sm], [fxFlexAlign.gt-sm] diff --git a/src/lib/flexbox/api/flex-offset.ts b/src/lib/flexbox/api/flex-offset.ts index b2706ad1f..3c6aa0762 100644 --- a/src/lib/flexbox/api/flex-offset.ts +++ b/src/lib/flexbox/api/flex-offset.ts @@ -28,7 +28,7 @@ import {MediaMonitor} from '../../media-query/media-monitor'; */ @Directive({selector: ` [fxFlexOffset], - [fxFlexOffset.xs] + [fxFlexOffset.xs], [fxFlexOffset.gt-xs], [fxFlexOffset.sm], [fxFlexOffset.gt-sm] diff --git a/src/lib/flexbox/api/flex.ts b/src/lib/flexbox/api/flex.ts index 1c1e12c61..4fb5412ee 100644 --- a/src/lib/flexbox/api/flex.ts +++ b/src/lib/flexbox/api/flex.ts @@ -41,7 +41,7 @@ export type FlexBasisAlias = 'grow' | 'initial' | 'auto' | 'none' | 'nogrow' | ' @Directive({ selector: ` [fxFlex], - [fxFlex.xs] + [fxFlex.xs], [fxFlex.gt-xs], [fxFlex.sm], [fxFlex.gt-sm] diff --git a/src/lib/flexbox/api/hide.spec.ts b/src/lib/flexbox/api/hide.spec.ts index 8284f327d..46d14a750 100644 --- a/src/lib/flexbox/api/hide.spec.ts +++ b/src/lib/flexbox/api/hide.spec.ts @@ -5,7 +5,9 @@ * 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 {Component, OnInit, Inject} from '@angular/core'; +import { + Component, OnInit, Inject +} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ComponentFixture, TestBed} from '@angular/core/testing'; @@ -13,12 +15,15 @@ import {MockMatchMedia} from '../../media-query/mock/mock-match-media'; import {MatchMedia, MatchMediaObservable} from '../../media-query/match-media'; import {BreakPointsProvider} from '../../media-query/providers/break-points-provider'; import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry'; -import {FlexLayoutModule} from '../_module'; import {customMatchers} from '../../utils/testing/custom-matchers'; -import {makeCreateTestComponent, expectNativeEl} from '../../utils/testing/helpers'; +import { + makeCreateTestComponent, expectNativeEl, +} from '../../utils/testing/helpers'; +import {HideDirective} from './hide'; +import {MediaQueriesModule} from '../../media-query/_module'; -describe('show directive', () => { +describe('hide directive', () => { let fixture: ComponentFixture; let createTestComponent = makeCreateTestComponent(() => TestHideComponent); let activateMediaQuery = (alias) => { @@ -29,10 +34,11 @@ describe('show directive', () => { beforeEach(() => { jasmine.addMatchers(customMatchers); + // Configure testbed to prepare services TestBed.configureTestingModule({ - imports: [CommonModule, FlexLayoutModule.forRoot()], - declarations: [TestHideComponent], + imports: [CommonModule, MediaQueriesModule.forRoot()], + declarations: [TestHideComponent, HideDirective], providers: [ BreakPointRegistry, BreakPointsProvider, {provide: MatchMedia, useClass: MockMatchMedia} @@ -77,7 +83,7 @@ describe('show directive', () => { it('should update styles with binding changes', () => { fixture = createTestComponent(` -
+
...content
`); @@ -92,7 +98,7 @@ describe('show directive', () => { describe('with responsive features', () => { - it('should show on `xs` viewports only', () => { + it('should show on `xs` viewports only when the default is included', () => { fixture = createTestComponent(`
...content @@ -108,6 +114,52 @@ describe('show directive', () => { }); + it('should preserve display and update only on activated mediaQuery', () => { + fixture = createTestComponent(` +
+ `); + expectNativeEl(fixture).toHaveCssStyle({'display': 'inline-block'}); + + // should hide with this activation + activateMediaQuery('xs'); + expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); + + // should reset to original display style + activateMediaQuery('md'); + expectNativeEl(fixture).toHaveCssStyle({'display': 'inline-block'}); + }); + + it('should restore original display when disabled', () => { + fixture = createTestComponent(` +
+ `); + expectNativeEl(fixture).toHaveCssStyle({'display': 'inline-block'}); + + // should hide with this activation + activateMediaQuery('xs'); + expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); + + // should reset to original display style + fixture.componentInstance.isHidden = false; + expectNativeEl(fixture).toHaveCssStyle({'display': 'inline-block'}); + }); + + it('should restore original display when the mediaQuery deactivates', () => { + let originalDisplay = {'display': 'table'}; + fixture = createTestComponent(` +
+ `); + expectNativeEl(fixture).toHaveCssStyle(originalDisplay); + + // should hide with this activation + activateMediaQuery('xs'); + expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); + + // should reset to original display style + activateMediaQuery('md'); + expectNativeEl(fixture).toHaveCssStyle(originalDisplay); + }); + it('should support use of the `media` observable in templates ', () => { fixture = createTestComponent(`
@@ -150,7 +202,8 @@ describe('show directive', () => { }) export class TestHideComponent implements OnInit { isVisible = 0; - menuHidden: boolean = true; + isHidden = true; + menuHidden = true; constructor(@Inject(MatchMediaObservable) private media) { } diff --git a/src/lib/flexbox/api/hide.ts b/src/lib/flexbox/api/hide.ts index 48af45d02..0ac56ce1e 100644 --- a/src/lib/flexbox/api/hide.ts +++ b/src/lib/flexbox/api/hide.ts @@ -30,9 +30,10 @@ import {LayoutDirective} from './layout'; * 'show' Layout API directive * */ -@Directive({selector: ` +@Directive({ + selector: ` [fxHide], - [fxHide.xs] + [fxHide.xs], [fxHide.gt-xs], [fxHide.sm], [fxHide.gt-sm] @@ -41,38 +42,63 @@ import {LayoutDirective} from './layout'; [fxHide.lg], [fxHide.gt-lg], [fxHide.xl] -`}) +` +}) export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * Original dom Elements CSS display style - */ - private _display = 'flex'; /** - * Subscription to the parent flex container's layout changes. - * Stored so we can unsubscribe when this directive is destroyed. - */ + * Subscription to the parent flex container's layout changes. + * Stored so we can unsubscribe when this directive is destroyed. + */ private _layoutWatcher: Subscription; - @Input('fxHide') set hide(val) { this._cacheInput("hide", val); } - @Input('fxHide.xs') set hideXs(val) { this._cacheInput('hideXs', val); } - @Input('fxHide.gt-xs') set hideGtXs(val) { this._cacheInput('hideGtXs', val); }; - @Input('fxHide.sm') set hideSm(val) { this._cacheInput('hideSm', val); }; - @Input('fxHide.gt-sm') set hideGtSm(val) { this._cacheInput('hideGtSm', val); }; - @Input('fxHide.md') set hideMd(val) { this._cacheInput('hideMd', val); }; - @Input('fxHide.gt-md') set hideGtMd(val) { this._cacheInput('hideGtMd', val); }; - @Input('fxHide.lg') set hideLg(val) { this._cacheInput('hideLg', val); }; - @Input('fxHide.gt-lg') set hideGtLg(val) { this._cacheInput('hideGtLg', val); }; - @Input('fxHide.xl') set hideXl(val) { this._cacheInput('hideXl', val); }; + @Input('fxHide') set hide(val) { + this._cacheInput("hide", val); + } + + @Input('fxHide.xs') set hideXs(val) { + this._cacheInput('hideXs', val); + } + + @Input('fxHide.gt-xs') set hideGtXs(val) { + this._cacheInput('hideGtXs', val); + }; + + @Input('fxHide.sm') set hideSm(val) { + this._cacheInput('hideSm', val); + }; + + @Input('fxHide.gt-sm') set hideGtSm(val) { + this._cacheInput('hideGtSm', val); + }; + + @Input('fxHide.md') set hideMd(val) { + this._cacheInput('hideMd', val); + }; + + @Input('fxHide.gt-md') set hideGtMd(val) { + this._cacheInput('hideGtMd', val); + }; + + @Input('fxHide.lg') set hideLg(val) { + this._cacheInput('hideLg', val); + }; + + @Input('fxHide.gt-lg') set hideGtLg(val) { + this._cacheInput('hideGtLg', val); + }; + + @Input('fxHide.xl') set hideXl(val) { + this._cacheInput('hideXl', val); + }; /** * */ - constructor( - monitor: MediaMonitor, - @Optional() @Self() private _layout: LayoutDirective, - protected elRef: ElementRef, - protected renderer: Renderer) { + constructor(monitor: MediaMonitor, + @Optional() @Self() private _layout: LayoutDirective, + protected elRef: ElementRef, + protected renderer: Renderer) { super(monitor, elRef, renderer); if (_layout) { @@ -105,7 +131,8 @@ export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, * mql change events to onMediaQueryChange handlers */ ngOnInit() { - this._listenForMediaQueryChanges('hide', true, (changes: MediaChange) => { + let value = this._getDefaultVal("hide", false); + this._listenForMediaQueryChanges('hide', value, (changes: MediaChange) => { this._updateWithValue(changes.value); }); this._updateWithValue(); @@ -127,7 +154,7 @@ export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, * Validate the visibility value and then update the host's inline display style */ private _updateWithValue(value?: string|number|boolean) { - value = value || this._queryInput("hide") || true; + value = value || this._getDefaultVal("hide", false); if (this._mqActivation) { value = this._mqActivation.activatedInput; } @@ -141,7 +168,7 @@ export class HideDirective extends BaseFxDirective implements OnInit, OnChanges, * Build the CSS that should be assigned to the element instance */ private _buildCSS(value) { - return {'display': value ? 'none' : this._display }; + return {'display': value ? 'none' : this._display}; } /** diff --git a/src/lib/flexbox/api/layout-align.ts b/src/lib/flexbox/api/layout-align.ts index 98b190c34..850d9c2be 100644 --- a/src/lib/flexbox/api/layout-align.ts +++ b/src/lib/flexbox/api/layout-align.ts @@ -37,7 +37,7 @@ import {LAYOUT_VALUES, LayoutDirective} from './layout'; */ @Directive({selector: ` [fxLayoutAlign], - [fxLayoutAlign.xs] + [fxLayoutAlign.xs], [fxLayoutAlign.gt-xs], [fxLayoutAlign.sm], [fxLayoutAlign.gt-sm] diff --git a/src/lib/flexbox/api/layout-gap.ts b/src/lib/flexbox/api/layout-gap.ts index bb37536c4..786a9cadf 100644 --- a/src/lib/flexbox/api/layout-gap.ts +++ b/src/lib/flexbox/api/layout-gap.ts @@ -30,7 +30,7 @@ import {LayoutDirective, LAYOUT_VALUES} from './layout'; */ @Directive({selector: ` [fxLayoutGap], - [fxLayoutGap.xs] + [fxLayoutGap.xs], [fxLayoutGap.gt-xs], [fxLayoutGap.sm], [fxLayoutGap.gt-sm] diff --git a/src/lib/flexbox/api/layout-wrap.ts b/src/lib/flexbox/api/layout-wrap.ts index 9720fa062..35582df85 100644 --- a/src/lib/flexbox/api/layout-wrap.ts +++ b/src/lib/flexbox/api/layout-wrap.ts @@ -31,7 +31,7 @@ import {LayoutDirective, LAYOUT_VALUES} from './layout'; */ @Directive({selector: ` [fxLayoutWrap], - [fxLayoutWrap.xs] + [fxLayoutWrap.xs], [fxLayoutWrap.gt-xs], [fxLayoutWrap.sm], [fxLayoutWrap.gt-sm] diff --git a/src/lib/flexbox/api/layout.ts b/src/lib/flexbox/api/layout.ts index c9cc12d7b..f5df7b50e 100644 --- a/src/lib/flexbox/api/layout.ts +++ b/src/lib/flexbox/api/layout.ts @@ -33,7 +33,7 @@ export const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse']; */ @Directive({selector: ` [fxLayout], - [fxLayout.xs] + [fxLayout.xs], [fxLayout.gt-xs], [fxLayout.sm], [fxLayout.gt-sm] diff --git a/src/lib/flexbox/api/show.spec.ts b/src/lib/flexbox/api/show.spec.ts index b0b7588ac..b8850cebf 100644 --- a/src/lib/flexbox/api/show.spec.ts +++ b/src/lib/flexbox/api/show.spec.ts @@ -130,6 +130,55 @@ describe('show directive', () => { expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); }); + it('should preserve display and update only on activated mediaQuery', () => { + let visibleStyle = {'display': 'inline-block'}; + fixture = createTestComponent(` +
+ `); + fixture.componentInstance.isHidden = false; + expectNativeEl(fixture).toHaveCssStyle(visibleStyle); + + // should hide with this activation and setting + activateMediaQuery('xs'); + fixture.componentInstance.isHidden = true; + expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); + }); + + it('should restore display when not enabled', () => { + let visibleStyle = {'display': 'inline-block'}; + fixture = createTestComponent(` +
+ `); + fixture.componentInstance.isHidden = false; + expectNativeEl(fixture).toHaveCssStyle(visibleStyle); + + // mqActivation but the isHidden == false, so show it + activateMediaQuery('xs'); + expectNativeEl(fixture).toHaveCssStyle(visibleStyle); + + // should hide with this activation + fixture.componentInstance.isHidden = true; + expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); + }); + + it('should restore display when the mediaQuery deactivates', () => { + let visibleStyle = {'display': 'inline-block'}; + fixture = createTestComponent(` +
+ `); + fixture.componentInstance.isHidden = true; + expectNativeEl(fixture).toHaveCssStyle(visibleStyle); + + // should hide with this activation + activateMediaQuery('xs'); + expectNativeEl(fixture).toHaveCssStyle({'display': 'none'}); + + // should reset to original display style + activateMediaQuery('md'); + expectNativeEl(fixture).toHaveCssStyle(visibleStyle); + }); + + }); }); @@ -144,6 +193,7 @@ describe('show directive', () => { }) export class TestShowComponent implements OnInit { isVisible = 0; + isHidden = false; menuOpen: boolean = true; constructor(@Inject(MatchMediaObservable) private media) { diff --git a/src/lib/flexbox/api/show.ts b/src/lib/flexbox/api/show.ts index 7b7d176e0..d923a61ba 100644 --- a/src/lib/flexbox/api/show.ts +++ b/src/lib/flexbox/api/show.ts @@ -27,16 +27,16 @@ import {MediaMonitor} from '../../media-query/media-monitor'; import {LayoutDirective} from './layout'; - const FALSY = ['false', false, 0]; /** * 'show' Layout API directive * */ -@Directive({selector: ` +@Directive({ + selector: ` [fxShow], - [fxShow.xs] + [fxShow.xs], [fxShow.gt-xs], [fxShow.sm], [fxShow.gt-sm] @@ -45,37 +45,63 @@ const FALSY = ['false', false, 0]; [fxShow.lg], [fxShow.gt-lg], [fxShow.xl] -`}) +` +}) export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { - /** - * Original dom Elements CSS display style - */ - private _display = 'flex'; /** - * Subscription to the parent flex container's layout changes. - * Stored so we can unsubscribe when this directive is destroyed. - */ + * Subscription to the parent flex container's layout changes. + * Stored so we can unsubscribe when this directive is destroyed. + */ private _layoutWatcher: Subscription; - @Input('fxShow') set show(val) { this._cacheInput("show", val); } - @Input('fxShow.xs') set showXs(val) { this._cacheInput('showXs', val); } - @Input('fxShow.gt-xs') set showGtXs(val) { this._cacheInput('showGtXs', val); }; - @Input('fxShow.sm') set showSm(val) { this._cacheInput('showSm', val); }; - @Input('fxShow.gt-sm') set showGtSm(val) { this._cacheInput('showGtSm', val); }; - @Input('fxShow.md') set showMd(val) { this._cacheInput('showMd', val); }; - @Input('fxShow.gt-md') set showGtMd(val) { this._cacheInput('showGtMd', val); }; - @Input('fxShow.lg') set showLg(val) { this._cacheInput('showLg', val); }; - @Input('fxShow.gt-lg') set showGtLg(val) { this._cacheInput('showGtLg', val); }; - @Input('fxShow.xl') set showXl(val) { this._cacheInput('showXl', val); }; + @Input('fxShow') set show(val) { + this._cacheInput("show", val); + } + + @Input('fxShow.xs') set showXs(val) { + this._cacheInput('showXs', val); + } + + @Input('fxShow.gt-xs') set showGtXs(val) { + this._cacheInput('showGtXs', val); + }; + + @Input('fxShow.sm') set showSm(val) { + this._cacheInput('showSm', val); + }; + + @Input('fxShow.gt-sm') set showGtSm(val) { + this._cacheInput('showGtSm', val); + }; + + @Input('fxShow.md') set showMd(val) { + this._cacheInput('showMd', val); + }; + + @Input('fxShow.gt-md') set showGtMd(val) { + this._cacheInput('showGtMd', val); + }; + + @Input('fxShow.lg') set showLg(val) { + this._cacheInput('showLg', val); + }; + + @Input('fxShow.gt-lg') set showGtLg(val) { + this._cacheInput('showGtLg', val); + }; + + @Input('fxShow.xl') set showXl(val) { + this._cacheInput('showXl', val); + }; + /** * */ - constructor( - monitor: MediaMonitor, - @Optional() @Self() private _layout: LayoutDirective, - protected elRef: ElementRef, - protected renderer: Renderer) { + constructor(monitor: MediaMonitor, + @Optional() @Self() private _layout: LayoutDirective, + protected elRef: ElementRef, + protected renderer: Renderer) { super(monitor, elRef, renderer); @@ -108,18 +134,20 @@ export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, * mql change events to onMediaQueryChange handlers */ ngOnInit() { - this._listenForMediaQueryChanges('show', true, (changes: MediaChange) => { + let value = this._getDefaultVal("show", true); + + this._listenForMediaQueryChanges('show', value, (changes: MediaChange) => { this._updateWithValue(changes.value); }); this._updateWithValue(); } ngOnDestroy() { - super.ngOnDestroy(); - if (this._layoutWatcher) { - this._layoutWatcher.unsubscribe(); - } - } + super.ngOnDestroy(); + if (this._layoutWatcher) { + this._layoutWatcher.unsubscribe(); + } + } // ********************************************* // Protected methods @@ -127,7 +155,7 @@ export class ShowDirective extends BaseFxDirective implements OnInit, OnChanges, /** Validate the visibility value and then update the host's inline display style */ private _updateWithValue(value?: string|number|boolean) { - value = value || this._queryInput("show") || true; + value = value || this._getDefaultVal("show", true); if (this._mqActivation) { value = this._mqActivation.activatedInput; }