diff --git a/src/apps/demo-app/src/app/github-issues/split/split-area.directive.ts b/src/apps/demo-app/src/app/github-issues/split/split-area.directive.ts index 5e9610e3e..9d12464d2 100644 --- a/src/apps/demo-app/src/app/github-issues/split/split-area.directive.ts +++ b/src/apps/demo-app/src/app/github-issues/split/split-area.directive.ts @@ -1,5 +1,5 @@ import {Directive, Optional, Self} from '@angular/core'; -import {FlexDirective} from '@angular/flex-layout'; +import {DefaultFlexDirective} from '@angular/flex-layout'; @Directive({ selector: '[ngxSplitArea]', @@ -8,5 +8,5 @@ import {FlexDirective} from '@angular/flex-layout'; } }) export class SplitAreaDirective { - constructor(@Optional() @Self() public flex: FlexDirective) {} + constructor(@Optional() @Self() public flex: DefaultFlexDirective) {} } diff --git a/src/apps/demo-app/src/app/github-issues/split/split.directive.ts b/src/apps/demo-app/src/app/github-issues/split/split.directive.ts index 7b2145923..836933ce9 100644 --- a/src/apps/demo-app/src/app/github-issues/split/split.directive.ts +++ b/src/apps/demo-app/src/app/github-issues/split/split.directive.ts @@ -54,7 +54,7 @@ export class SplitDirective implements AfterContentInit, OnDestroy { const currentValue = flex.activatedValue; // Update Flex-Layout value to build/inject new flexbox CSS - flex.activatedValue = this.calculateSize(currentValue, delta); + flex.activatedValue = `${this.calculateSize(currentValue, delta)}`; }); } diff --git a/src/apps/universal-app/src/app/split/split-area.directive.ts b/src/apps/universal-app/src/app/split/split-area.directive.ts index 7a4e015b1..9cbb034db 100644 --- a/src/apps/universal-app/src/app/split/split-area.directive.ts +++ b/src/apps/universal-app/src/app/split/split-area.directive.ts @@ -1,5 +1,5 @@ import {Directive, Optional, Self} from '@angular/core'; -import {FlexDirective} from '@angular/flex-layout'; +import {DefaultFlexDirective} from '@angular/flex-layout'; @Directive({ selector: '[ngxSplitArea]', @@ -8,5 +8,5 @@ import {FlexDirective} from '@angular/flex-layout'; } }) export class SplitAreaDirective { - constructor(@Optional() @Self() public flex: FlexDirective) { } + constructor(@Optional() @Self() public flex: DefaultFlexDirective) { } } diff --git a/src/apps/universal-app/src/app/split/split.directive.ts b/src/apps/universal-app/src/app/split/split.directive.ts index e01f70854..55d492995 100644 --- a/src/apps/universal-app/src/app/split/split.directive.ts +++ b/src/apps/universal-app/src/app/split/split.directive.ts @@ -60,7 +60,7 @@ export class SplitDirective implements AfterContentInit, OnDestroy { const currentValue = flex.activatedValue; // Update Flex-Layout value to build/inject new flexbox CSS - flex.activatedValue = this.calculateSize(currentValue, delta); + flex.activatedValue = `${this.calculateSize(currentValue, delta)}`; }); } diff --git a/src/lib/core/add-alias.ts b/src/lib/core/add-alias.ts index ae9206fa0..e419198e7 100644 --- a/src/lib/core/add-alias.ts +++ b/src/lib/core/add-alias.ts @@ -13,7 +13,7 @@ import {extendObject} from '../utils/object-extend'; * For the specified MediaChange, make sure it contains the breakpoint alias * and suffix (if available). */ -export function mergeAlias(dest: MediaChange, source: BreakPoint | null) { +export function mergeAlias(dest: MediaChange, source: BreakPoint | null): MediaChange { return extendObject(dest, source ? { mqAlias: source.alias, suffix: source.suffix diff --git a/src/lib/core/base/base-adapter.ts b/src/lib/core/base/base-adapter.ts index 5451b7fa9..cf3d16d98 100644 --- a/src/lib/core/base/base-adapter.ts +++ b/src/lib/core/base/base-adapter.ts @@ -17,6 +17,8 @@ import {StyleUtils} from '../style-utils/style-utils'; /** * Adapter to the BaseDirective abstract class so it can be used via composition. * @see BaseDirective + * @deprecated + * @deletion-target v7.0.0-beta.21 */ export class BaseDirectiveAdapter extends BaseDirective { diff --git a/src/lib/core/base/base.ts b/src/lib/core/base/base.ts index 14249170b..45cd47d79 100644 --- a/src/lib/core/base/base.ts +++ b/src/lib/core/base/base.ts @@ -23,7 +23,11 @@ import {MediaMonitor} from '../media-monitor/media-monitor'; import {MediaQuerySubscriber} from '../media-change'; import {StyleBuilder} from '../style-builder/style-builder'; -/** Abstract base class for the Layout API styling directives. */ +/** + * Abstract base class for the Layout API styling directives. + * @deprecated + * @deletion-target v7.0.0-beta.21 + */ export abstract class BaseDirective implements OnDestroy, OnChanges { /** diff --git a/src/lib/core/base/base2.ts b/src/lib/core/base/base2.ts new file mode 100644 index 000000000..309c148d7 --- /dev/null +++ b/src/lib/core/base/base2.ts @@ -0,0 +1,122 @@ +/** + * @license + * Copyright Google LLC 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 {ElementRef, OnChanges, OnDestroy, SimpleChanges} from '@angular/core'; +import {Subject} from 'rxjs'; + +import {StyleDefinition, StyleUtils} from '../style-utils/style-utils'; +import {StyleBuilder} from '../style-builder/style-builder'; +import {MediaMarshaller} from '../media-marshaller/media-marshaller'; +import {buildLayoutCSS} from '../../utils/layout-validator'; + +export abstract class BaseDirective2 implements OnChanges, OnDestroy { + + protected DIRECTIVE_KEY = ''; + protected inputs: string[] = []; + protected destroySubject: Subject = new Subject(); + + /** Access to host element's parent DOM node */ + protected get parentElement(): HTMLElement | null { + return this.elementRef.nativeElement.parentElement; + } + + /** Access to the HTMLElement for the directive */ + protected get nativeElement(): HTMLElement { + return this.elementRef.nativeElement; + } + + /** Access to the activated value for the directive */ + get activatedValue(): string { + return this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY); + } + set activatedValue(value: string) { + this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, value, + this.marshal.activatedBreakpoint); + } + + /** Cache map for style computation */ + protected styleCache: Map = new Map(); + + protected constructor(protected elementRef: ElementRef, + protected styleBuilder: StyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + } + + /** For @Input changes */ + ngOnChanges(changes: SimpleChanges) { + Object.keys(changes).forEach(key => { + if (this.inputs.indexOf(key) !== -1) { + const bp = key.split('.')[1] || ''; + const val = changes[key].currentValue; + this.setValue(val, bp); + } + }); + } + + ngOnDestroy(): void { + this.destroySubject.next(); + this.destroySubject.complete(); + this.marshal.releaseElement(this.nativeElement); + } + + /** Add styles to the element using predefined style builder */ + protected addStyles(input: string, parent?: Object) { + const builder = this.styleBuilder; + const useCache = builder.shouldCache; + + let genStyles: StyleDefinition | undefined = this.styleCache.get(input); + + if (!genStyles || !useCache) { + genStyles = builder.buildStyles(input, parent); + if (useCache) { + this.styleCache.set(input, genStyles); + } + } + + this.applyStyleToElement(genStyles); + builder.sideEffect(input, genStyles, parent); + } + + protected triggerUpdate() { + const val = this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY); + this.marshal.updateElement(this.nativeElement, this.DIRECTIVE_KEY, val); + } + + /** + * Determine the DOM element's Flexbox flow (flex-direction). + * + * Check inline style first then check computed (stylesheet) style. + * And optionally add the flow value to element's inline style. + */ + protected getFlexFlowDirection(target: HTMLElement, addIfMissing = false): string { + if (target) { + const [value, hasInlineValue] = this.styler.getFlowDirection(target); + + if (!hasInlineValue && addIfMissing) { + const style = buildLayoutCSS(value); + const elements = [target]; + this.styler.applyStyleToElements(style, elements); + } + + return value.trim(); + } + + return 'row'; + } + + /** Applies styles given via string pair or object map to the directive element */ + protected applyStyleToElement(style: StyleDefinition, + value?: string | number, + element: HTMLElement = this.nativeElement) { + this.styler.applyStyleToElement(element, style, value); + } + + protected setValue(val: any, bp: string): void { + this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, val, bp); + } +} diff --git a/src/lib/core/base/index.ts b/src/lib/core/base/index.ts index 855382060..dd0a27eb6 100644 --- a/src/lib/core/base/index.ts +++ b/src/lib/core/base/index.ts @@ -8,3 +8,4 @@ export * from './base'; export * from './base-adapter'; +export * from './base2'; diff --git a/src/lib/core/breakpoints/break-point.ts b/src/lib/core/breakpoints/break-point.ts index 3e4cc9bb1..e3d07dd71 100644 --- a/src/lib/core/breakpoints/break-point.ts +++ b/src/lib/core/breakpoints/break-point.ts @@ -10,4 +10,6 @@ export interface BreakPoint { alias: string; suffix?: string; overlapping?: boolean; + // The priority of the individual breakpoint when overlapping another breakpoint + priority?: number; } diff --git a/src/lib/core/breakpoints/breakpoint-tools.ts b/src/lib/core/breakpoints/breakpoint-tools.ts index 6a9a9aa5c..73df2fb30 100644 --- a/src/lib/core/breakpoints/breakpoint-tools.ts +++ b/src/lib/core/breakpoints/breakpoint-tools.ts @@ -64,3 +64,9 @@ export function mergeByAlias(defaults: BreakPoint[], custom: BreakPoint[] = []): return validateSuffixes(Object.keys(dict).map(k => dict[k])); } +/** HOF to sort the breakpoints by priority */ +export function prioritySort(a: BreakPoint, b: BreakPoint): number { + const priorityA = a.priority || 0; + const priorityB = b.priority || 0; + return priorityB - priorityA; +} diff --git a/src/lib/core/breakpoints/data/break-points.ts b/src/lib/core/breakpoints/data/break-points.ts index cc149df33..c83f6f4a5 100644 --- a/src/lib/core/breakpoints/data/break-points.ts +++ b/src/lib/core/breakpoints/data/break-points.ts @@ -14,63 +14,76 @@ export const RESPONSIVE_ALIASES = [ export const DEFAULT_BREAKPOINTS: BreakPoint[] = [ { alias: 'xs', - mediaQuery: '(min-width: 0px) and (max-width: 599px)' + mediaQuery: '(min-width: 0px) and (max-width: 599px)', + priority: 100, }, { alias: 'gt-xs', overlapping: true, - mediaQuery: '(min-width: 600px)' + mediaQuery: '(min-width: 600px)', + priority: 7, }, { alias: 'lt-sm', overlapping: true, - mediaQuery: '(max-width: 599px)' + mediaQuery: '(max-width: 599px)', + priority: 10, }, { alias: 'sm', - mediaQuery: '(min-width: 600px) and (max-width: 959px)' + mediaQuery: '(min-width: 600px) and (max-width: 959px)', + priority: 100, }, { alias: 'gt-sm', overlapping: true, - mediaQuery: '(min-width: 960px)' + mediaQuery: '(min-width: 960px)', + priority: 8, }, { alias: 'lt-md', overlapping: true, - mediaQuery: '(max-width: 959px)' + mediaQuery: '(max-width: 959px)', + priority: 9, }, { alias: 'md', - mediaQuery: '(min-width: 960px) and (max-width: 1279px)' + mediaQuery: '(min-width: 960px) and (max-width: 1279px)', + priority: 100, }, { alias: 'gt-md', overlapping: true, - mediaQuery: '(min-width: 1280px)' + mediaQuery: '(min-width: 1280px)', + priority: 9, }, { alias: 'lt-lg', overlapping: true, - mediaQuery: '(max-width: 1279px)' + mediaQuery: '(max-width: 1279px)', + priority: 8, }, { alias: 'lg', - mediaQuery: '(min-width: 1280px) and (max-width: 1919px)' + mediaQuery: '(min-width: 1280px) and (max-width: 1919px)', + priority: 100, }, { alias: 'gt-lg', overlapping: true, - mediaQuery: '(min-width: 1920px)' + mediaQuery: '(min-width: 1920px)', + priority: 10, }, { alias: 'lt-xl', overlapping: true, - mediaQuery: '(max-width: 1919px)' + mediaQuery: '(max-width: 1919px)', + priority: 7, }, { alias: 'xl', - mediaQuery: '(min-width: 1920px) and (max-width: 5000px)' + mediaQuery: '(min-width: 1920px) and (max-width: 5000px)', + priority: 100, } ]; diff --git a/src/lib/core/breakpoints/index.ts b/src/lib/core/breakpoints/index.ts index 18c379038..6bf81c5b0 100644 --- a/src/lib/core/breakpoints/index.ts +++ b/src/lib/core/breakpoints/index.ts @@ -12,3 +12,4 @@ export * from './data/orientation-break-points'; export * from './break-point'; export * from './break-point-registry'; export * from './break-points-token'; +export {prioritySort} from './breakpoint-tools'; diff --git a/src/lib/core/match-media/mock/mock-match-media.ts b/src/lib/core/match-media/mock/mock-match-media.ts index 838fc789e..8b61dca9d 100644 --- a/src/lib/core/match-media/mock/mock-match-media.ts +++ b/src/lib/core/match-media/mock/mock-match-media.ts @@ -79,32 +79,32 @@ export class MockMatchMedia extends MatchMedia { // Simulate activation of overlapping lt- ranges switch (alias) { case 'lg' : - this._activateByAlias('lt-xl'); + this._activateByAlias('lt-xl', true); break; case 'md' : - this._activateByAlias('lt-xl, lt-lg'); + this._activateByAlias('lt-xl, lt-lg', true); break; case 'sm' : - this._activateByAlias('lt-xl, lt-lg, lt-md'); + this._activateByAlias('lt-xl, lt-lg, lt-md', true); break; case 'xs' : - this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm'); + this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm', true); break; } // Simulate activate of overlapping gt- mediaQuery ranges switch (alias) { case 'xl' : - this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs'); + this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs', true); break; case 'lg' : - this._activateByAlias('gt-md, gt-sm, gt-xs'); + this._activateByAlias('gt-md, gt-sm, gt-xs', true); break; case 'md' : - this._activateByAlias('gt-sm, gt-xs'); + this._activateByAlias('gt-sm, gt-xs', true); break; case 'sm' : - this._activateByAlias('gt-xs'); + this._activateByAlias('gt-xs', true); break; } } @@ -115,10 +115,10 @@ export class MockMatchMedia extends MatchMedia { /** * */ - private _activateByAlias(aliases: string) { + private _activateByAlias(aliases: string, useOverlaps = false) { const activate = (alias: string) => { const bp = this._breakpoints.findByAlias(alias); - this._activateByQuery(bp ? bp.mediaQuery : alias); + this._activateByQuery(bp ? bp.mediaQuery : alias, useOverlaps); }; aliases.split(',').forEach(alias => activate(alias.trim())); } @@ -126,10 +126,13 @@ export class MockMatchMedia extends MatchMedia { /** * */ - private _activateByQuery(mediaQuery: string) { - const mql = this._registry.get(mediaQuery)!; + private _activateByQuery(mediaQuery: string, useOverlaps = false) { + if (useOverlaps) { + this._registerMediaQuery(mediaQuery); + } + const mql = this._registry.get(mediaQuery); const alreadyAdded = this._actives - .reduce((found, it) => (found || (mql && (it.media === mql.media))), false); + .reduce((found, it) => (found || (mql ? (it.media === mql.media) : false)), false); if (mql && !alreadyAdded) { this._actives.push(mql.activate()); diff --git a/src/lib/core/media-marshaller/media-marshaller.spec.ts b/src/lib/core/media-marshaller/media-marshaller.spec.ts new file mode 100644 index 000000000..5fac1d60a --- /dev/null +++ b/src/lib/core/media-marshaller/media-marshaller.spec.ts @@ -0,0 +1,130 @@ +/** + * @license + * Copyright Google LLC 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 {async, fakeAsync, inject, TestBed} from '@angular/core/testing'; +import {Subject} from 'rxjs'; + +import {MockMatchMedia, MockMatchMediaProvider} from '../match-media/mock/mock-match-media'; +import {MatchMedia} from '../match-media/match-media'; +import {MediaMarshaller} from './media-marshaller'; + +describe('media-marshaller', () => { + let matchMedia: MockMatchMedia; + let mediaMarshaller: MediaMarshaller; + + beforeEach(() => { + // Configure testbed to prepare services + TestBed.configureTestingModule({ + providers: [MockMatchMediaProvider] + }); + spyOn(MediaMarshaller.prototype, 'activate').and.callThrough(); + }); + + // Single async inject to save references; which are used in all tests below + beforeEach(async(inject([MatchMedia, MediaMarshaller], + (service: MockMatchMedia, marshal: MediaMarshaller) => { + matchMedia = service; // inject only to manually activate mediaQuery ranges + mediaMarshaller = marshal; + }))); + afterEach(() => { + matchMedia.clearAll(); + }); + + it('activates when match-media activates', () => { + matchMedia.activate('xs'); + expect(mediaMarshaller.activate).toHaveBeenCalled(); + }); + + it('should set correct activated breakpoint', () => { + matchMedia.activate('lg'); + expect(mediaMarshaller.activatedBreakpoint).toBe('lg'); + + matchMedia.activate('gt-md'); + expect(mediaMarshaller.activatedBreakpoint).toBe('gt-md'); + }); + + it('should init', () => { + let triggered = false; + const builder = () => { + triggered = true; + }; + mediaMarshaller.init(fakeElement, fakeKey, builder); + mediaMarshaller.setValue(fakeElement, fakeKey, 0, 'xs'); + triggered = false; + matchMedia.activate('xs'); + expect(triggered).toBeTruthy(); + }); + + it('should init with observables', () => { + let triggered = false; + const subject: Subject = new Subject(); + const obs = subject.asObservable(); + const builder = () => { + triggered = true; + }; + mediaMarshaller.init(fakeElement, fakeKey, builder, [obs]); + subject.next(); + expect(triggered).toBeTruthy(); + }); + + it('should updateStyles', () => { + let triggered = false; + const builder = () => { + triggered = true; + }; + mediaMarshaller.init(fakeElement, fakeKey, builder); + mediaMarshaller.setValue(fakeElement, fakeKey, 0, ''); + triggered = false; + mediaMarshaller.updateStyles(); + expect(triggered).toBeTruthy(); + }); + + it('should updateElement', () => { + let triggered = false; + const builder = () => { + triggered = true; + }; + mediaMarshaller.init(fakeElement, fakeKey, builder); + mediaMarshaller.updateElement(fakeElement, fakeKey, 0); + expect(triggered).toBeTruthy(); + }); + + it('should get the right value', () => { + const builder = () => {}; + mediaMarshaller.init(fakeElement, fakeKey, builder); + mediaMarshaller.setValue(fakeElement, fakeKey, 0, ''); + const value = mediaMarshaller.getValue(fakeElement, fakeKey); + expect(value).toEqual(0); + }); + + it('should track changes', fakeAsync(() => { + const builder = () => {}; + let triggered = false; + mediaMarshaller.init(fakeElement, fakeKey, builder); + mediaMarshaller.trackValue(fakeElement, fakeKey).subscribe(() => { + triggered = true; + }); + mediaMarshaller.setValue(fakeElement, fakeKey, 0, ''); + expect(triggered).toBeTruthy(); + })); + + it('should release', () => { + let triggered = false; + const subject: Subject = new Subject(); + const obs = subject.asObservable(); + const builder = () => { + triggered = true; + }; + mediaMarshaller.init(fakeElement, fakeKey, builder, [obs]); + mediaMarshaller.releaseElement(fakeElement); + subject.next(); + expect(triggered).toBeFalsy(); + }); +}); + +const fakeElement = {} as HTMLElement; +const fakeKey = 'FAKE_KEY'; diff --git a/src/lib/core/media-marshaller/media-marshaller.ts b/src/lib/core/media-marshaller/media-marshaller.ts new file mode 100644 index 000000000..644abe7f4 --- /dev/null +++ b/src/lib/core/media-marshaller/media-marshaller.ts @@ -0,0 +1,227 @@ +/** + * @license + * Copyright Google LLC 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 {Injectable} from '@angular/core'; +import {merge, Observable, Subject, Subscription} from 'rxjs'; +import {filter} from 'rxjs/operators'; + +import {BreakPoint} from '../breakpoints/break-point'; +import {prioritySort} from '../breakpoints/breakpoint-tools'; +import {BreakPointRegistry} from '../breakpoints/break-point-registry'; +import {MatchMedia} from '../match-media/match-media'; +import {MediaChange} from '../media-change'; + +type Builder = Function; +type ValueMap = Map; +type BreakpointMap = Map; +type ElementMap = Map; +type SubscriptionMap = Map; +type WatcherMap = WeakMap; +type BuilderMap = WeakMap>; + +export interface ElementMatcher { + element: HTMLElement; + key: string; + value: any; +} + +/** + * MediaMarshaller - register responsive values from directives and + * trigger them based on media query events + */ +@Injectable({providedIn: 'root'}) +export class MediaMarshaller { + private activatedBreakpoints: BreakPoint[] = []; + private elementMap: ElementMap = new Map(); + private watcherMap: WatcherMap = new WeakMap(); + private builderMap: BuilderMap = new WeakMap(); + private subject: Subject = new Subject(); + + get activatedBreakpoint(): string { + return this.activatedBreakpoints[0] ? this.activatedBreakpoints[0].alias : ''; + } + + constructor(protected matchMedia: MatchMedia, + protected breakpoints: BreakPointRegistry) { + this.matchMedia.observe().subscribe(this.activate.bind(this)); + } + + /** + * activate or deactivate a given breakpoint + * @param mc + */ + activate(mc: MediaChange) { + const bp: BreakPoint | null = this.findByQuery(mc.mediaQuery); + if (mc.matches && bp) { + this.activatedBreakpoints.push(bp); + this.activatedBreakpoints.sort(prioritySort); + } else if (!mc.matches && bp) { + // Remove the breakpoint when it's deactivated + this.activatedBreakpoints.splice(this.activatedBreakpoints.indexOf(bp), 1); + } + this.updateStyles(); + } + + /** + * initialize the marshaller with necessary elements for delegation on an element + * @param element + * @param key + * @param builder optional so that custom bp directives don't have to re-provide this + * @param observables + */ + init(element: HTMLElement, + key: string, + builder?: Builder, + observables: Observable[] = []): void { + if (builder) { + let builders = this.builderMap.get(element); + if (!builders) { + builders = new Map(); + this.builderMap.set(element, builders); + } + builders.set(key, builder); + } + if (observables) { + let watchers = this.watcherMap.get(element); + if (!watchers) { + watchers = new Map(); + this.watcherMap.set(element, watchers); + } + const subscription = watchers.get(key); + if (!subscription) { + const newSubscription = merge(...observables).subscribe(() => { + const currentValue = this.getValue(element, key); + this.updateElement(element, key, currentValue); + }); + watchers.set(key, newSubscription); + } + } + } + + /** + * get the value for an element and key and optionally a given breakpoint + * @param element + * @param key + * @param bp + */ + getValue(element: HTMLElement, key: string, bp?: string): any { + const bpMap = this.elementMap.get(element); + if (bpMap) { + const values = bp !== undefined ? bpMap.get(bp) : this.getFallback(bpMap); + if (values) { + const value = values.get(key); + return value !== undefined ? value : ''; + } + } + return ''; + } + + /** + * whether the element has values for a given key + * @param element + * @param key + */ + hasValue(element: HTMLElement, key: string): boolean { + const bpMap = this.elementMap.get(element); + if (bpMap) { + const values = this.getFallback(bpMap); + if (values) { + return values.get(key) !== undefined || false; + } + } + return false; + } + + /** + * Set the value for an input on a directive + * @param element the element in question + * @param key the type of the directive (e.g. flex, layout-gap, etc) + * @param bp the breakpoint suffix (empty string = default) + * @param val the value for the breakpoint + */ + setValue(element: HTMLElement, key: string, val: any, bp: string): void { + let bpMap: BreakpointMap | undefined = this.elementMap.get(element); + if (!bpMap) { + bpMap = new Map().set(bp, new Map().set(key, val)); + this.elementMap.set(element, bpMap); + } else { + const values = (bpMap.get(bp) || new Map()).set(key, val); + bpMap.set(bp, values); + this.elementMap.set(element, bpMap); + } + this.updateElement(element, key, this.getValue(element, key)); + } + + trackValue(element: HTMLElement, key: string): Observable { + return this.subject.asObservable() + .pipe(filter(v => v.element === element && v.key === key)); + } + + /** update all styles for all elements on the current breakpoint */ + updateStyles(): void { + this.elementMap.forEach((bpMap, el) => { + const valueMap = this.getFallback(bpMap); + if (valueMap) { + valueMap.forEach((v, k) => this.updateElement(el, k, v)); + } + }); + } + + /** + * update a given element with the activated values for a given key + * @param element + * @param key + * @param value + */ + updateElement(element: HTMLElement, key: string, value: any): void { + const builders = this.builderMap.get(element); + if (builders) { + const builder: Builder | undefined = builders.get(key); + if (builder) { + builder(value); + this.subject.next({element, key, value}); + } + } + } + + /** + * release all references to a given element + * @param element + */ + releaseElement(element: HTMLElement): void { + const watcherMap = this.watcherMap.get(element); + if (watcherMap) { + watcherMap.forEach(s => s.unsubscribe()); + this.watcherMap.delete(element); + } + const elementMap = this.elementMap.get(element); + if (elementMap) { + elementMap.forEach((_, s) => elementMap.delete(s)); + this.elementMap.delete(element); + } + } + + /** Breakpoint locator by mediaQuery */ + private findByQuery(query: string) { + return this.breakpoints.findByQuery(query); + } + + /** + * get the fallback breakpoint for a given element, starting with the current breakpoint + * @param bpMap + */ + private getFallback(bpMap: BreakpointMap): ValueMap | undefined { + for (let i = 0; i < this.activatedBreakpoints.length; i++) { + const activatedBp = this.activatedBreakpoints[i]; + const valueMap = bpMap.get(activatedBp.alias); + if (valueMap) { + return valueMap; + } + } + return bpMap.get(''); + } +} diff --git a/src/lib/core/media-monitor/media-monitor.ts b/src/lib/core/media-monitor/media-monitor.ts index 8454029cb..ceb3d9097 100644 --- a/src/lib/core/media-monitor/media-monitor.ts +++ b/src/lib/core/media-monitor/media-monitor.ts @@ -28,6 +28,8 @@ import {mergeAlias} from '../add-alias'; * - injects alias information into each raw MediaChange event * - provides accessor to the currently active BreakPoint * - publish list of overlapping BreakPoint(s); used by ResponsiveActivation + * @deprecated + * @deletion-target v7.0.0-beta.21 */ @Injectable({providedIn: 'root'}) export class MediaMonitor { diff --git a/src/lib/core/module.ts b/src/lib/core/module.ts index 5f89388b3..576f7438f 100644 --- a/src/lib/core/module.ts +++ b/src/lib/core/module.ts @@ -8,6 +8,7 @@ import {NgModule} from '@angular/core'; import {BROWSER_PROVIDER} from './browser-provider'; +import {ObservableMediaProvider} from './observable-media/observable-media'; /** * ***************************************************************** @@ -16,7 +17,7 @@ import {BROWSER_PROVIDER} from './browser-provider'; */ @NgModule({ - providers: [BROWSER_PROVIDER] + providers: [BROWSER_PROVIDER, ObservableMediaProvider] }) export class CoreModule { } diff --git a/src/lib/core/public-api.ts b/src/lib/core/public-api.ts index 8c23ddfb0..9faead727 100644 --- a/src/lib/core/public-api.ts +++ b/src/lib/core/public-api.ts @@ -23,3 +23,4 @@ export * from './responsive-activation/responsive-activation'; export * from './style-utils/style-utils'; export * from './style-builder/style-builder'; export * from './basis-validator/basis-validator'; +export * from './media-marshaller/media-marshaller'; diff --git a/src/lib/core/responsive-activation/responsive-activation.ts b/src/lib/core/responsive-activation/responsive-activation.ts index f09c7c9f8..9ff38b241 100644 --- a/src/lib/core/responsive-activation/responsive-activation.ts +++ b/src/lib/core/responsive-activation/responsive-activation.ts @@ -13,11 +13,19 @@ import {BreakPoint} from '../breakpoints/break-point'; import {MediaMonitor} from '../media-monitor/media-monitor'; import {extendObject} from '../../utils/object-extend'; +/** + * @deprecated + * @deletion-target v7.0.0-beta.21 + */ export interface BreakPointX extends BreakPoint { key: string; baseKey: string; } +/** + * @deprecated + * @deletion-target v7.0.0-beta.21 + */ export class KeyOptions { constructor(public baseKey: string, public defaultValue: string|number|boolean, @@ -36,6 +44,8 @@ export class KeyOptions { * MediaQueryServices. * * NOTE: these interceptions enables the logic in the fx API directives to remain terse and clean. + * @deprecated + * @deletion-target v7.0.0-beta.21 */ export class ResponsiveActivation { private _activatedInputKey: string = ''; diff --git a/src/lib/extended/class/class.spec.ts b/src/lib/extended/class/class.spec.ts index aba666397..21d3c7f3d 100644 --- a/src/lib/extended/class/class.spec.ts +++ b/src/lib/extended/class/class.spec.ts @@ -18,7 +18,7 @@ import { import {customMatchers, expect} from '../../utils/testing/custom-matchers'; import {makeCreateTestComponent, expectNativeEl, queryFor} from '../../utils/testing/helpers'; -import {ClassDirective} from './class'; +import {DefaultClassDirective} from './class'; describe('class directive', () => { @@ -44,7 +44,7 @@ describe('class directive', () => { CommonModule, CoreModule ], - declarations: [TestClassComponent, ClassDirective], + declarations: [TestClassComponent, DefaultClassDirective], providers: [MockMatchMediaProvider] }); }); diff --git a/src/lib/extended/class/class.ts b/src/lib/extended/class/class.ts index c41e9e68e..bcb740065 100644 --- a/src/lib/extended/class/class.ts +++ b/src/lib/extended/class/class.ts @@ -12,155 +12,80 @@ import { Input, IterableDiffers, KeyValueDiffers, - OnChanges, - OnDestroy, Optional, Renderer2, - SimpleChanges, Self, - OnInit, } from '@angular/core'; import {NgClass} from '@angular/common'; -import { - BaseDirective, - BaseDirectiveAdapter, - MediaChange, - MediaMonitor, - StyleUtils, -} from '@angular/flex-layout/core'; - +import {BaseDirective2, StyleUtils, MediaMarshaller} from '@angular/flex-layout/core'; -/** NgClass allowed inputs **/ -export type NgClassType = string | string[] | Set | {[klass: string]: any}; +export class ClassDirective extends BaseDirective2 implements DoCheck { -/** - * Directive to add responsive support for ngClass. - * This maintains the core functionality of 'ngClass' and adds responsive API - * Note: this class is a no-op when rendered on the server - */ -@Directive({ - selector: ` - [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], - [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], - [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg] - ` -}) -export class ClassDirective extends BaseDirective - implements DoCheck, OnChanges, OnDestroy, OnInit { - - /** - * Intercept ngClass assignments so we cache the default classes - * which are merged with activated styles or used as fallbacks. - * Note: Base ngClass values are applied during ngDoCheck() - */ - @Input('ngClass') - set ngClassBase(val: NgClassType) { - const key = 'ngClass'; - this._base.cacheInput(key, val, true); - this._ngClassInstance.ngClass = this._base.queryInput(key); - } + protected DIRECTIVE_KEY = 'ngClass'; /** * Capture class assignments so we cache the default classes * which are merged with activated styles and used as fallbacks. */ @Input('class') - set klazz(val: string) { - const key = 'class'; - this._base.cacheInput(key, val); - this._ngClassInstance.klass = val; + set klass(val: string) { + this.ngClassInstance.klass = val; + this.setValue(val, ''); } - /* tslint:disable */ - @Input('ngClass.xs') set ngClassXs(val: NgClassType) { this._base.cacheInput('ngClassXs', val, true); } - @Input('ngClass.sm') set ngClassSm(val: NgClassType) { this._base.cacheInput('ngClassSm', val, true); } - @Input('ngClass.md') set ngClassMd(val: NgClassType) { this._base.cacheInput('ngClassMd', val, true); } - @Input('ngClass.lg') set ngClassLg(val: NgClassType) { this._base.cacheInput('ngClassLg', val, true); } - @Input('ngClass.xl') set ngClassXl(val: NgClassType) { this._base.cacheInput('ngClassXl', val, true); } - - @Input('ngClass.lt-sm') set ngClassLtSm(val: NgClassType) { this._base.cacheInput('ngClassLtSm', val, true); } - @Input('ngClass.lt-md') set ngClassLtMd(val: NgClassType) { this._base.cacheInput('ngClassLtMd', val, true); } - @Input('ngClass.lt-lg') set ngClassLtLg(val: NgClassType) { this._base.cacheInput('ngClassLtLg', val, true); } - @Input('ngClass.lt-xl') set ngClassLtXl(val: NgClassType) { this._base.cacheInput('ngClassLtXl', val, true); } - - @Input('ngClass.gt-xs') set ngClassGtXs(val: NgClassType) { this._base.cacheInput('ngClassGtXs', val, true); } - @Input('ngClass.gt-sm') set ngClassGtSm(val: NgClassType) { this._base.cacheInput('ngClassGtSm', val, true); } - @Input('ngClass.gt-md') set ngClassGtMd(val: NgClassType) { this._base.cacheInput('ngClassGtMd', val, true); } - @Input('ngClass.gt-lg') set ngClassGtLg(val: NgClassType) { this._base.cacheInput('ngClassGtLg', val, true); } - - /* tslint:enable */ - constructor(protected monitor: MediaMonitor, - protected _iterableDiffers: IterableDiffers, - protected _keyValueDiffers: KeyValueDiffers, - protected _ngEl: ElementRef, - protected _renderer: Renderer2, - @Optional() @Self() private readonly _ngClassInstance: NgClass, - protected _styler: StyleUtils) { - super(monitor, _ngEl, _styler); - this._base = new BaseDirectiveAdapter( - 'ngClass', - this.monitor, - this._ngEl, - this._styler - ); - if (!this._ngClassInstance) { + constructor(protected elementRef: ElementRef, + protected styler: StyleUtils, + protected marshal: MediaMarshaller, + protected iterableDiffers: IterableDiffers, + protected keyValueDiffers: KeyValueDiffers, + protected renderer: Renderer2, + @Optional() @Self() protected readonly ngClassInstance: NgClass) { + super(elementRef, null!, styler, marshal); + if (!this.ngClassInstance) { // Create an instance NgClass Directive instance only if `ngClass=""` has NOT been defined on // the same host element; since the responsive variations may be defined... - this._ngClassInstance = new NgClass( - this._iterableDiffers, this._keyValueDiffers, this._ngEl, this._renderer + this.ngClassInstance = new NgClass( + this.iterableDiffers, this.keyValueDiffers, this.elementRef, this.renderer ); } + this.marshal.init(this.nativeElement, this.DIRECTIVE_KEY, this.updateWithValue.bind(this)); + } + + protected updateWithValue(value: any) { + this.ngClassInstance.ngClass = value; + this.ngClassInstance.ngDoCheck(); } // ****************************************************************** // Lifecycle Hooks // ****************************************************************** - /** - * For @Input changes on the current mq activation property - */ - ngOnChanges(changes: SimpleChanges) { - if (this._base.activeKey in changes) { - this._ngClassInstance.ngClass = this._base.mqActivation.activatedInput || ''; - } - } - - ngOnInit() { - this._configureMQListener(); - } - /** * For ChangeDetectionStrategy.onPush and ngOnChanges() updates */ ngDoCheck() { - this._ngClassInstance.ngDoCheck(); + this.ngClassInstance.ngDoCheck(); } +} - ngOnDestroy() { - this._base.ngOnDestroy(); - } +const inputs = [ + 'ngClass', 'ngClass.xs', 'ngClass.sm', 'ngClass.md', 'ngClass.lg', 'ngClass.xl', + 'ngClass.lt-sm', 'ngClass.lt-md', 'ngClass.lt-lg', 'ngClass.lt-xl', + 'ngClass.gt-xs', 'ngClass.gt-sm', 'ngClass.gt-md', 'ngClass.gt-lg' +]; - // ****************************************************************** - // Internal Methods - // ****************************************************************** +const selector = ` + [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], + [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], + [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg] +`; - /** - * Build an mqActivation object that bridges mql change events to onMediaQueryChange handlers - * NOTE: We delegate subsequent activity to the NgClass logic - * Identify the activated input value and update the ngClass iterables... - * Use ngDoCheck() to actually apply the values to the element - */ - protected _configureMQListener(baseKey = 'ngClass') { - const fallbackValue = this._base.queryInput(baseKey); - this._base.listenForMediaQueryChanges(baseKey, fallbackValue, (changes: MediaChange) => { - this._ngClassInstance.ngClass = changes.value || ''; - this._ngClassInstance.ngDoCheck(); - }); - } - - /** - * Special adapter to cross-cut responsive behaviors and capture mediaQuery changes - * Delegate value changes to the internal `_ngClassInstance` for processing - */ - protected _base: BaseDirectiveAdapter; // used for `ngClass.xxx` selectors +/** + * Directive to add responsive support for ngClass. + * This maintains the core functionality of 'ngClass' and adds responsive API + * Note: this class is a no-op when rendered on the server + */ +@Directive({selector, inputs}) +export class DefaultClassDirective extends ClassDirective { + protected inputs = inputs; } diff --git a/src/lib/extended/img-src/img-src.ts b/src/lib/extended/img-src/img-src.ts index 0050bec66..a6d19de03 100644 --- a/src/lib/extended/img-src/img-src.ts +++ b/src/lib/extended/img-src/img-src.ts @@ -5,96 +5,46 @@ * 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 { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - Inject, - Optional, - PLATFORM_ID, -} from '@angular/core'; +import {Directive, ElementRef, Inject, PLATFORM_ID, Injectable, Input} from '@angular/core'; import {isPlatformServer} from '@angular/common'; import { - BaseDirective, - MediaMonitor, + MediaMarshaller, + BaseDirective2, SERVER_TOKEN, + StyleBuilder, + StyleDefinition, StyleUtils, } from '@angular/flex-layout/core'; - -/** - * This directive provides a responsive API for the HTML 'src' attribute - * and will update the img.src property upon each responsive activation. - * - * e.g. - * - * - * @see https://css-tricks.com/responsive-images-youre-just-changing-resolutions-use-src/ - */ -@Directive({ - selector: ` - img[src.xs], img[src.sm], img[src.md], img[src.lg], img[src.xl], - img[src.lt-sm], img[src.lt-md], img[src.lt-lg], img[src.lt-xl], - img[src.gt-xs], img[src.gt-sm], img[src.gt-md], img[src.gt-lg] -` -}) -export class ImgSrcDirective extends BaseDirective implements OnInit, OnChanges { - - /* tslint:disable */ - @Input('src') set srcBase(val: string) { this.cacheDefaultSrc(val); } - - @Input('src.xs') set srcXs(val: string) { this._cacheInput('srcXs', val); } - @Input('src.sm') set srcSm(val: string) { this._cacheInput('srcSm', val); } - @Input('src.md') set srcMd(val: string) { this._cacheInput('srcMd', val); } - @Input('src.lg') set srcLg(val: string) { this._cacheInput('srcLg', val); } - @Input('src.xl') set srcXl(val: string) { this._cacheInput('srcXl', val); } - - @Input('src.lt-sm') set srcLtSm(val: string) { this._cacheInput('srcLtSm', val); } - @Input('src.lt-md') set srcLtMd(val: string) { this._cacheInput('srcLtMd', val); } - @Input('src.lt-lg') set srcLtLg(val: string) { this._cacheInput('srcLtLg', val); } - @Input('src.lt-xl') set srcLtXl(val: string) { this._cacheInput('srcLtXl', val); } - - @Input('src.gt-xs') set srcGtXs(val: string) { this._cacheInput('srcGtXs', val); } - @Input('src.gt-sm') set srcGtSm(val: string) { this._cacheInput('srcGtSm', val); } - @Input('src.gt-md') set srcGtMd(val: string) { this._cacheInput('srcGtMd', val); } - @Input('src.gt-lg') set srcGtLg(val: string) { this._cacheInput('srcGtLg', val); } - /* tslint:enable */ - - constructor(protected _elRef: ElementRef, - protected _monitor: MediaMonitor, - protected _styler: StyleUtils, - @Inject(PLATFORM_ID) protected _platformId: Object, - @Optional() @Inject(SERVER_TOKEN) protected _serverModuleLoaded: boolean) { - super(_monitor, _elRef, _styler); - this._cacheInput('src', _elRef.nativeElement.getAttribute('src') || ''); - if (isPlatformServer(this._platformId) && this._serverModuleLoaded) { - this.nativeElement.setAttribute('src', ''); - } +@Injectable({providedIn: 'root'}) +export class ImgSrcStyleBuilder extends StyleBuilder { + buildStyles(url: string) { + return {'content': url ? `url(${url})` : ''}; } +} - /** - * Listen for responsive changes to update the img.src attribute - */ - ngOnInit() { - super.ngOnInit(); +export class ImgSrcDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'img-src'; + protected defaultSrc = ''; - if (this.hasResponsiveKeys) { - // Listen for responsive changes - this._listenForMediaQueryChanges('src', this.defaultSrc, () => { - this._updateSrcFor(); - }); - } - this._updateSrcFor(); + @Input('src') + set src(val: string) { + this.defaultSrc = val; + this.setValue('', this.defaultSrc); } - /** - * Update the 'src' property of the host element - */ - ngOnChanges() { - if (this.hasInitialized) { - this._updateSrcFor(); + constructor(protected elementRef: ElementRef, + protected styleBuilder: ImgSrcStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller, + @Inject(PLATFORM_ID) protected platformId: Object, + @Inject(SERVER_TOKEN) protected serverModuleLoaded: boolean) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateSrcFor.bind(this)); + this.setValue('', this.nativeElement.getAttribute('src') || ''); + if (isPlatformServer(this.platformId) && this.serverModuleLoaded) { + this.nativeElement.setAttribute('src', ''); } } @@ -106,40 +56,42 @@ export class ImgSrcDirective extends BaseDirective implements OnInit, OnChanges * Do nothing to standard `` usages, only when responsive * keys are present do we actually call `setAttribute()` */ - protected _updateSrcFor() { - if (this.hasResponsiveKeys) { - let url = this.activatedValue || this.defaultSrc; - if (isPlatformServer(this._platformId) && this._serverModuleLoaded) { - this._styler.applyStyleToElement(this.nativeElement, {'content': url ? `url(${url})` : ''}); - } else { - this.nativeElement.setAttribute('src', String(url)); - } + protected updateSrcFor() { + let url = this.activatedValue || this.defaultSrc; + if (isPlatformServer(this.platformId) && this.serverModuleLoaded) { + this.addStyles(url); + } else { + this.nativeElement.setAttribute('src', String(url)); } } - /** - * Cache initial value of 'src', this will be used as fallback when breakpoint - * activations change. - * NOTE: The default 'src' property is not bound using @Input(), so perform - * a post-ngOnInit() lookup of the default src value (if any). - */ - protected cacheDefaultSrc(value?: string) { - this._cacheInput('src', value || ''); - } + protected styleCache = imgSrcCache; +} - /** - * Empty values are maintained, undefined values are exposed as '' - */ - protected get defaultSrc(): string { - return this._queryInput('src') || ''; - } +const imgSrcCache: Map = new Map(); - /** - * Does the have 1 or more src. responsive inputs - * defined... these will be mapped to activated breakpoints. - */ - protected get hasResponsiveKeys() { - return Object.keys(this._inputMap).length > 1; - } +const inputs = [ + 'src.xs', 'src.sm', 'src.md', 'src.lg', 'src.xl', + 'src.lt-sm', 'src.lt-md', 'src.lt-lg', 'src.lt-xl', + 'src.gt-xs', 'src.gt-sm', 'src.gt-md', 'src.gt-lg' +]; +const selector = ` + img[src.xs], img[src.sm], img[src.md], img[src.lg], img[src.xl], + img[src.lt-sm], img[src.lt-md], img[src.lt-lg], img[src.lt-xl], + img[src.gt-xs], img[src.gt-sm], img[src.gt-md], img[src.gt-lg] +`; + +/** + * This directive provides a responsive API for the HTML 'src' attribute + * and will update the img.src property upon each responsive activation. + * + * e.g. + * + * + * @see https://css-tricks.com/responsive-images-youre-just-changing-resolutions-use-src/ + */ +@Directive({selector, inputs}) +export class DefaultImgSrcDirective extends ImgSrcDirective { + protected inputs = inputs; } diff --git a/src/lib/extended/module.ts b/src/lib/extended/module.ts index e38773cbb..5f0e77ae2 100644 --- a/src/lib/extended/module.ts +++ b/src/lib/extended/module.ts @@ -8,17 +8,17 @@ import {NgModule} from '@angular/core'; import {CoreModule} from '@angular/flex-layout/core'; -import {ImgSrcDirective} from './img-src/img-src'; -import {ClassDirective} from './class/class'; -import {ShowHideDirective} from './show-hide/show-hide'; -import {StyleDirective} from './style/style'; +import {DefaultImgSrcDirective} from './img-src/img-src'; +import {DefaultClassDirective} from './class/class'; +import {DefaultShowHideDirective} from './show-hide/show-hide'; +import {DefaultStyleDirective} from './style/style'; const ALL_DIRECTIVES = [ - ShowHideDirective, - ClassDirective, - StyleDirective, - ImgSrcDirective + DefaultShowHideDirective, + DefaultClassDirective, + DefaultStyleDirective, + DefaultImgSrcDirective ]; /** diff --git a/src/lib/extended/show-hide/hide.spec.ts b/src/lib/extended/show-hide/hide.spec.ts index f89b8f6ea..898a3fcb5 100644 --- a/src/lib/extended/show-hide/hide.spec.ts +++ b/src/lib/extended/show-hide/hide.spec.ts @@ -22,7 +22,7 @@ import {customMatchers, expect, NgMatchers} from '../../utils/testing/custom-mat import { makeCreateTestComponent, expectNativeEl, queryFor } from '../../utils/testing/helpers'; -import {ShowHideDirective} from './show-hide'; +import {DefaultShowHideDirective} from './show-hide'; describe('hide directive', () => { @@ -60,7 +60,7 @@ describe('hide directive', () => { // Configure testbed to prepare services TestBed.configureTestingModule({ imports: [CommonModule, CoreModule], - declarations: [TestHideComponent, ShowHideDirective], + declarations: [TestHideComponent, DefaultShowHideDirective], providers: [ MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true}, diff --git a/src/lib/extended/show-hide/show-hide.ts b/src/lib/extended/show-hide/show-hide.ts index 97cf05d13..968077b97 100644 --- a/src/lib/extended/show-hide/show-hide.ts +++ b/src/lib/extended/show-hide/show-hide.ts @@ -8,135 +8,104 @@ import { Directive, ElementRef, - Input, - OnInit, OnChanges, - OnDestroy, SimpleChanges, - Self, Optional, Inject, PLATFORM_ID, - ViewChild, + Injectable, AfterViewInit, } from '@angular/core'; import {isPlatformServer} from '@angular/common'; import { - BaseDirective, + BaseDirective2, LAYOUT_CONFIG, LayoutConfigOptions, - MediaChange, - MediaMonitor, + MediaMarshaller, SERVER_TOKEN, StyleUtils, + StyleBuilder, } from '@angular/flex-layout/core'; -import {FlexDirective, LayoutDirective} from '@angular/flex-layout/flex'; -import {Subscription} from 'rxjs'; - -const FALSY = ['false', false, 0]; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {takeUntil} from 'rxjs/operators'; /** * For fxHide selectors, we invert the 'value' * and assign to the equivalent fxShow selector cache * - When 'hide' === '' === true, do NOT show the element * - When 'hide' === false or 0... we WILL show the element + * @deprecated + * @deletion-target v7.0.0-beta.21 */ export function negativeOf(hide: any) { return (hide === '') ? false : ((hide === 'false') || (hide === 0)) ? true : !hide; } -/** - * 'show' Layout API directive - * - */ -@Directive({ - selector: ` - [fxShow], - [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], - [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], - [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], - [fxHide], - [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], - [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], - [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg] -` -}) -export class ShowHideDirective extends BaseDirective - implements OnInit, OnChanges, OnDestroy, AfterViewInit { +export interface ShowHideParent { + display: string; +} - /** - * Subscription to the parent flex container's layout changes. - * Stored so we can unsubscribe when this directive is destroyed. - */ - protected _layoutWatcher?: Subscription; +@Injectable({providedIn: 'root'}) +export class ShowHideStyleBuilder extends StyleBuilder { + buildStyles(show: string, parent: ShowHideParent) { + const shouldShow = show === 'true'; + return {'display': shouldShow ? parent.display : 'none'}; + } +} + +export class ShowHideDirective extends BaseDirective2 implements AfterViewInit, OnChanges { + protected DIRECTIVE_KEY = 'show-hide'; /** Original dom Elements CSS display style */ - protected _display: string = ''; - - /* tslint:disable */ - @Input('fxShow') set show(val: string) { this._cacheInput('show', val); } - @Input('fxShow.xs') set showXs(val: string) {this._cacheInput('showXs', val);} - @Input('fxShow.sm') set showSm(val: string) {this._cacheInput('showSm', val); }; - @Input('fxShow.md') set showMd(val: string) {this._cacheInput('showMd', val); }; - @Input('fxShow.lg') set showLg(val: string) {this._cacheInput('showLg', val); }; - @Input('fxShow.xl') set showXl(val: string) {this._cacheInput('showXl', val); }; - - @Input('fxShow.lt-sm') set showLtSm(val: string) { this._cacheInput('showLtSm', val); }; - @Input('fxShow.lt-md') set showLtMd(val: string) { this._cacheInput('showLtMd', val); }; - @Input('fxShow.lt-lg') set showLtLg(val: string) { this._cacheInput('showLtLg', val); }; - @Input('fxShow.lt-xl') set showLtXl(val: string) { this._cacheInput('showLtXl', val); }; - - @Input('fxShow.gt-xs') set showGtXs(val: string) {this._cacheInput('showGtXs', val); }; - @Input('fxShow.gt-sm') set showGtSm(val: string) {this._cacheInput('showGtSm', val); }; - @Input('fxShow.gt-md') set showGtMd(val: string) {this._cacheInput('showGtMd', val); }; - @Input('fxShow.gt-lg') set showGtLg(val: string) {this._cacheInput('showGtLg', val); }; - - @Input('fxHide') set hide(val: string) {this._cacheInput('show', negativeOf(val));} - @Input('fxHide.xs') set hideXs(val: string) {this._cacheInput('showXs', negativeOf(val));} - @Input('fxHide.sm') set hideSm(val: string) {this._cacheInput('showSm', negativeOf(val)); }; - @Input('fxHide.md') set hideMd(val: string) {this._cacheInput('showMd', negativeOf(val)); }; - @Input('fxHide.lg') set hideLg(val: string) {this._cacheInput('showLg', negativeOf(val)); }; - @Input('fxHide.xl') set hideXl(val: string) {this._cacheInput('showXl', negativeOf(val)); }; - - @Input('fxHide.lt-sm') set hideLtSm(val: string) { this._cacheInput('showLtSm', negativeOf(val)); }; - @Input('fxHide.lt-md') set hideLtMd(val: string) { this._cacheInput('showLtMd', negativeOf(val)); }; - @Input('fxHide.lt-lg') set hideLtLg(val: string) { this._cacheInput('showLtLg', negativeOf(val)); }; - @Input('fxHide.lt-xl') set hideLtXl(val: string) { this._cacheInput('showLtXl', negativeOf(val)); }; - - @Input('fxHide.gt-xs') set hideGtXs(val: string) {this._cacheInput('showGtXs', negativeOf(val)); }; - @Input('fxHide.gt-sm') set hideGtSm(val: string) {this._cacheInput('showGtSm', negativeOf(val)); }; - @Input('fxHide.gt-md') set hideGtMd(val: string) {this._cacheInput('showGtMd', negativeOf(val)); }; - @Input('fxHide.gt-lg') set hideGtLg(val: string) {this._cacheInput('showGtLg', negativeOf(val)); }; - /* tslint:enable */ - - @ViewChild(FlexDirective) protected _flexChild: FlexDirective | null = null; - - constructor(monitor: MediaMonitor, - @Optional() @Self() protected layout: LayoutDirective, - protected elRef: ElementRef, - protected styleUtils: StyleUtils, + protected display: string = ''; + protected hasLayout = false; + protected hasFlexChild = false; + + constructor(protected elementRef: ElementRef, + protected styleBuilder: ShowHideStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller, + @Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions, @Inject(PLATFORM_ID) protected platformId: Object, - @Optional() @Inject(SERVER_TOKEN) protected serverModuleLoaded: boolean, - @Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions) { - - super(monitor, elRef, styleUtils); + @Optional() @Inject(SERVER_TOKEN) protected serverModuleLoaded: boolean) { + super(elementRef, styleBuilder, styler, marshal); } // ********************************************* // Lifecycle Methods // ********************************************* - /** - * Override accessor to the current HTMLElement's `display` style - * Note: Show/Hide will not change the display to 'flex' but will set it to 'block' - * unless it was already explicitly specified inline or in a CSS stylesheet. - */ - protected _getDisplayStyle(): string { - return (this.layout || (this._flexChild && this.layoutConfig.addFlexToParent)) ? - 'flex' : super._getDisplayStyle(); - } + ngAfterViewInit() { + this.hasLayout = this.marshal.hasValue(this.nativeElement, 'layout'); + this.marshal.trackValue(this.nativeElement, 'layout') + .pipe(takeUntil(this.destroySubject)) + .subscribe(this.updateWithValue.bind(this)); + + const children = Array.from(this.nativeElement.children); + for (let i = 0; i < children.length; i++) { + if (this.marshal.hasValue(children[i] as HTMLElement, 'flex')) { + this.hasFlexChild = true; + break; + } + } + if (DISPLAY_MAP.has(this.nativeElement)) { + this.display = DISPLAY_MAP.get(this.nativeElement)!; + } else { + this.display = this.getDisplayStyle(); + DISPLAY_MAP.set(this.nativeElement, this.display); + } + + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); + // set the default to show unless explicitly overridden + const defaultValue = this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY, ''); + if (defaultValue === undefined || defaultValue === '') { + this.setValue(true, ''); + } + this.updateWithValue(this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY)); + } /** * On changes to any @Input properties... @@ -144,76 +113,76 @@ export class ShowHideDirective extends BaseDirective * Then conditionally override with the mq-activated Input's current value */ ngOnChanges(changes: SimpleChanges) { - if (this.hasInitialized && (changes['show'] != null || this._mqActivation)) { - this._updateWithValue(); - } - } - - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - } - - ngAfterViewInit() { - if (DISPLAY_MAP.has(this.nativeElement)) { - this._display = DISPLAY_MAP.get(this.nativeElement)!; - } else { - this._display = this._getDisplayStyle(); - DISPLAY_MAP.set(this.nativeElement, this._display); - } - if (this.layout) { - /** - * The Layout can set the display:flex (and incorrectly affect the Hide/Show directives. - * Whenever Layout [on the same element] resets its CSS, then update the Hide/Show CSS - */ - this._layoutWatcher = this.layout.layout$.subscribe(() => this._updateWithValue()); - } - let value = this._getDefaultVal('show', true); - // Build _mqActivation controller - this._listenForMediaQueryChanges('show', value, (changes: MediaChange) => { - this._updateWithValue(changes.value); + Object.keys(changes).forEach(key => { + if (this.inputs.indexOf(key) !== -1) { + const inputKey = key.split('.'); + const bp = inputKey[1] || ''; + const inputValue = changes[key].currentValue; + let shouldShow = inputValue !== '' ? + inputValue !== 0 ? coerceBooleanProperty(inputValue) : false + : true; + if (inputKey[0] === 'fxHide') { + shouldShow = !shouldShow; + } + this.setValue(shouldShow, bp); + } }); - this._updateWithValue(); - } - - ngOnDestroy() { - super.ngOnDestroy(); - if (this._layoutWatcher) { - this._layoutWatcher.unsubscribe(); - } } // ********************************************* // Protected methods // ********************************************* + /** + * Override accessor to the current HTMLElement's `display` style + * Note: Show/Hide will not change the display to 'flex' but will set it to 'block' + * unless it was already explicitly specified inline or in a CSS stylesheet. + */ + protected getDisplayStyle(): string { + return (this.hasLayout || (this.hasFlexChild && this.layoutConfig.addFlexToParent)) ? + 'flex' : this.styler.lookupStyle(this.nativeElement, 'display', true); + } + /** Validate the visibility value and then update the host's inline display style */ - protected _updateWithValue(value?: string|number|boolean) { - value = value || this._getDefaultVal('show', true); - if (this._mqActivation) { - value = this._mqActivation.activatedInput; + protected updateWithValue(value: boolean|string = true) { + if (value === '') { + return; } - - let shouldShow = this._validateTruthy(value); - this._applyStyleToElement(this._buildCSS(shouldShow)); + this.addStyles(value ? 'true' : 'false', {display: this.display}); if (isPlatformServer(this.platformId) && this.serverModuleLoaded) { this.nativeElement.style.setProperty('display', ''); } } +} +const DISPLAY_MAP: WeakMap = new WeakMap(); - /** Build the CSS that should be assigned to the element instance */ - protected _buildCSS(show: boolean) { - return {'display': show ? this._display : 'none'}; - } +const inputs = [ + 'fxShow', + 'fxShow.xs', 'fxShow.sm', 'fxShow.md', 'fxShow.lg', 'fxShow.xl', + 'fxShow.lt-sm', 'fxShow.lt-md', 'fxShow.lt-lg', 'fxShow.lt-xl', + 'fxShow.gt-xs', 'fxShow.gt-sm', 'fxShow.gt-md', 'fxShow.gt-lg', + 'fxHide', + 'fxHide.xs', 'fxHide.sm', 'fxHide.md', 'fxHide.lg', 'fxHide.xl', + 'fxHide.lt-sm', 'fxHide.lt-md', 'fxHide.lt-lg', 'fxHide.lt-xl', + 'fxHide.gt-xs', 'fxHide.gt-sm', 'fxHide.gt-md', 'fxHide.gt-lg' +]; + +const selector = ` + [fxShow], + [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], + [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], + [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], + [fxHide], + [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], + [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], + [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg] +`; - /** Validate the to be not FALSY */ - _validateTruthy(show: string | number | boolean = '') { - return (FALSY.indexOf(show) === -1); - } +/** + * 'show' Layout API directive + */ +@Directive({selector, inputs}) +export class DefaultShowHideDirective extends ShowHideDirective { + protected inputs = inputs; } - -const DISPLAY_MAP: WeakMap = new WeakMap(); diff --git a/src/lib/extended/show-hide/show.spec.ts b/src/lib/extended/show-hide/show.spec.ts index d529d2d27..152293258 100644 --- a/src/lib/extended/show-hide/show.spec.ts +++ b/src/lib/extended/show-hide/show.spec.ts @@ -5,24 +5,11 @@ * 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, - Directive, - ElementRef, - Inject, - Input, - OnInit, - Optional, - PLATFORM_ID, - Self, -} from '@angular/core'; +import {Component, Directive, OnInit, PLATFORM_ID} from '@angular/core'; import {CommonModule, isPlatformBrowser} from '@angular/common'; import {ComponentFixture, TestBed, inject, async} from '@angular/core/testing'; import { - LAYOUT_CONFIG, - LayoutConfigOptions, MatchMedia, - MediaMonitor, MockMatchMedia, MockMatchMediaProvider, MediaObserver, @@ -42,8 +29,7 @@ import {MatFormFieldModule} from '@angular/material/form-field'; import {FormsModule} from '@angular/forms'; import {MatSelectModule} from '@angular/material/select'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {negativeOf, ShowHideDirective} from './show-hide'; -import {LayoutDirective} from '@angular/flex-layout/flex'; +import {ShowHideDirective} from './show-hide'; describe('show directive', () => { let fixture: ComponentFixture; @@ -268,25 +254,25 @@ describe('show directive', () => { `); - let selector = '[el]'; + const elSelector = '[el]'; matchMedia.useOverlaps = true; fixture.detectChanges(); // NOTE: platform-server can't compute display for unknown elements if (isPlatformBrowser(platformId)) { - expectEl(queryFor(fixture, selector)[0]).toHaveStyle({ + expectEl(queryFor(fixture, elSelector)[0]).toHaveCSS({ 'display': 'inline' }, styler); } else { - expectEl(queryFor(fixture, selector)[0]).not.toHaveStyle({ + expectEl(queryFor(fixture, elSelector)[0]).not.toHaveStyle({ 'display': '*' }, styler); } matchMedia.activate('xs'); fixture.detectChanges(); - expectEl(queryFor(fixture, selector)[0]).toHaveStyle({ + expectEl(queryFor(fixture, elSelector)[0]).toHaveStyle({ 'display': 'none' }, styler); @@ -294,11 +280,11 @@ describe('show directive', () => { fixture.detectChanges(); // NOTE: platform-server can't compute display for unknown elements if (isPlatformBrowser(platformId)) { - expectEl(queryFor(fixture, selector)[0]).toHaveStyle({ + expectEl(queryFor(fixture, elSelector)[0]).toHaveCSS({ 'display': 'inline' }, styler); } else { - expectEl(queryFor(fixture, selector)[0]).not.toHaveStyle({ + expectEl(queryFor(fixture, elSelector)[0]).not.toHaveStyle({ 'display': '*' }, styler); } @@ -348,26 +334,13 @@ describe('show directive', () => { }); -@Directive({ - selector: `[fxShow.sm-md], [fxHide.sm-md]` -}) -class FxShowHideDirective extends ShowHideDirective { - constructor(monitor: MediaMonitor, - @Optional() @Self() protected layout: LayoutDirective, - protected elRef: ElementRef, - protected styleUtils: StyleUtils, - @Inject(PLATFORM_ID) protected platformId: Object, - @Optional() @Inject(SERVER_TOKEN) protected serverModuleLoaded: boolean, - @Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions) { - super(monitor, layout, elRef, styleUtils, platformId, serverModuleLoaded, layoutConfig); - } +const inputs = ['fxShow.sm-md', 'fxHide.sm-md']; +const selector = `[fxShow.sm-md], [fxHide.sm-md]`; - @Input('fxShow.sm-md') set showSmMd(val: string) { - this._cacheInput('showSmMd', val); - } - @Input('fxHide.sm-md') set hideSmMd(val: string) { - this._cacheInput('showSmMd', negativeOf(val)); - } +// Used to test custom breakpoint functionality +@Directive({inputs, selector}) +class FxShowHideDirective extends ShowHideDirective { + protected inputs = inputs; } diff --git a/src/lib/extended/style/style.spec.ts b/src/lib/extended/style/style.spec.ts index f8fe88069..f25aed40e 100644 --- a/src/lib/extended/style/style.spec.ts +++ b/src/lib/extended/style/style.spec.ts @@ -15,9 +15,9 @@ import { MockMatchMediaProvider, StyleUtils, } from '@angular/flex-layout/core'; -import {LayoutDirective} from '@angular/flex-layout/flex'; +import {DefaultLayoutDirective} from '@angular/flex-layout/flex'; -import {StyleDirective} from './style'; +import {DefaultStyleDirective} from './style'; import {customMatchers} from '../../utils/testing/custom-matchers'; import { makeCreateTestComponent, expectNativeEl @@ -42,7 +42,7 @@ describe('style directive', () => { // Configure testbed to prepare services TestBed.configureTestingModule({ imports: [CommonModule, CoreModule], - declarations: [TestStyleComponent, LayoutDirective, StyleDirective], + declarations: [TestStyleComponent, DefaultLayoutDirective, DefaultStyleDirective], providers: [MockMatchMediaProvider] }); }); diff --git a/src/lib/extended/style/style.ts b/src/lib/extended/style/style.ts index 12136729e..98e758566 100644 --- a/src/lib/extended/style/style.ts +++ b/src/lib/extended/style/style.ts @@ -9,28 +9,16 @@ import { Directive, DoCheck, ElementRef, - Input, KeyValueDiffers, - OnDestroy, - OnChanges, Optional, Renderer2, SecurityContext, Self, - SimpleChanges, - OnInit, } from '@angular/core'; import {NgStyle} from '@angular/common'; import {DomSanitizer} from '@angular/platform-browser'; -import { - BaseDirective, - BaseDirectiveAdapter, - MediaChange, - MediaMonitor, - StyleUtils, -} from '@angular/flex-layout/core'; +import {BaseDirective2, StyleUtils, MediaMarshaller} from '@angular/flex-layout/core'; -import {extendObject} from '../../utils/object-extend'; import { NgStyleRawList, NgStyleType, @@ -44,130 +32,33 @@ import { keyValuesToMap, } from './style-transforms'; -/** - * Directive to add responsive support for ngStyle. - * - */ -@Directive({ - selector: ` - [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], - [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], - [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg] - ` -}) -export class StyleDirective extends BaseDirective implements DoCheck, OnChanges, OnDestroy, OnInit { - - /** - * Intercept ngStyle assignments so we cache the default styles - * which are merged with activated styles or used as fallbacks. - */ - @Input('ngStyle') - set ngStyleBase(val: NgStyleType) { - const key = 'ngStyle'; - this._base.cacheInput(key, val, true); // convert val to hashmap - this._ngStyleInstance.ngStyle = this._base.queryInput(key); - } - - /* tslint:disable */ - @Input('ngStyle.xs') set ngStyleXs(val: NgStyleType) { this._base.cacheInput('ngStyleXs', val, true); } - @Input('ngStyle.sm') set ngStyleSm(val: NgStyleType) { this._base.cacheInput('ngStyleSm', val, true); }; - @Input('ngStyle.md') set ngStyleMd(val: NgStyleType) { this._base.cacheInput('ngStyleMd', val, true); }; - @Input('ngStyle.lg') set ngStyleLg(val: NgStyleType) { this._base.cacheInput('ngStyleLg', val, true); }; - @Input('ngStyle.xl') set ngStyleXl(val: NgStyleType) { this._base.cacheInput('ngStyleXl', val, true); }; - - @Input('ngStyle.lt-sm') set ngStyleLtSm(val: NgStyleType) { this._base.cacheInput('ngStyleLtSm', val, true); }; - @Input('ngStyle.lt-md') set ngStyleLtMd(val: NgStyleType) { this._base.cacheInput('ngStyleLtMd', val, true); }; - @Input('ngStyle.lt-lg') set ngStyleLtLg(val: NgStyleType) { this._base.cacheInput('ngStyleLtLg', val, true); }; - @Input('ngStyle.lt-xl') set ngStyleLtXl(val: NgStyleType) { this._base.cacheInput('ngStyleLtXl', val, true); }; +export class StyleDirective extends BaseDirective2 implements DoCheck { - @Input('ngStyle.gt-xs') set ngStyleGtXs(val: NgStyleType) { this._base.cacheInput('ngStyleGtXs', val, true); }; - @Input('ngStyle.gt-sm') set ngStyleGtSm(val: NgStyleType) { this._base.cacheInput('ngStyleGtSm', val, true); } ; - @Input('ngStyle.gt-md') set ngStyleGtMd(val: NgStyleType) { this._base.cacheInput('ngStyleGtMd', val, true); }; - @Input('ngStyle.gt-lg') set ngStyleGtLg(val: NgStyleType) { this._base.cacheInput('ngStyleGtLg', val, true); }; - /* tslint:enable */ + protected DIRECTIVE_KEY = 'ngStyle'; - /** - * Constructor for the ngStyle subclass; which adds selectors and - * a MediaQuery Activation Adapter - */ - constructor(private monitor: MediaMonitor, - protected _sanitizer: DomSanitizer, - protected _ngEl: ElementRef, - protected _renderer: Renderer2, - protected _differs: KeyValueDiffers, - @Optional() @Self() private readonly _ngStyleInstance: NgStyle, - protected _styler: StyleUtils) { - super(monitor, _ngEl, _styler); - this._base = new BaseDirectiveAdapter( - 'ngStyle', - this.monitor, - this._ngEl, - this._styler - ); - if (!this._ngStyleInstance) { + constructor(protected elementRef: ElementRef, + protected styler: StyleUtils, + protected marshal: MediaMarshaller, + protected keyValueDiffers: KeyValueDiffers, + protected renderer: Renderer2, + protected sanitizer: DomSanitizer, + @Optional() @Self() private readonly ngStyleInstance: NgStyle) { + super(elementRef, null!, styler, marshal); + if (!this.ngStyleInstance) { // Create an instance NgClass Directive instance only if `ngClass=""` has NOT been // defined on the same host element; since the responsive variations may be defined... - this._ngStyleInstance = new NgStyle(this._differs, this._ngEl, this._renderer); + this.ngStyleInstance = new NgStyle(this.keyValueDiffers, this.elementRef, this.renderer); } - - this._buildCacheInterceptor(); - this._fallbackToStyle(); - } - - // ****************************************************************** - // Lifecycle Hooks - // ****************************************************************** - - /** For @Input changes on the current mq activation property */ - ngOnChanges(changes: SimpleChanges) { - if (this._base.activeKey in changes) { - this._ngStyleInstance.ngStyle = this._base.mqActivation.activatedInput || ''; - } - } - - ngOnInit() { - this._configureMQListener(); - } - - /** For ChangeDetectionStrategy.onPush and ngOnChanges() updates */ - ngDoCheck() { - this._ngStyleInstance.ngDoCheck(); + this.marshal.init(this.nativeElement, this.DIRECTIVE_KEY, this.updateWithValue.bind(this)); + this.setValue(this.nativeElement.getAttribute('style') || '', ''); } - ngOnDestroy() { - this._base.ngOnDestroy(); - } - - // ****************************************************************** - // Internal Methods - // ****************************************************************** - - /** - * Build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - protected _configureMQListener(baseKey = 'ngStyle') { - const fallbackValue = this._base.queryInput(baseKey); - this._base.listenForMediaQueryChanges(baseKey, fallbackValue, (changes: MediaChange) => { - this._ngStyleInstance.ngStyle = changes.value || ''; - this._ngStyleInstance.ngDoCheck(); - }); - } - - // ************************************************************************ - // Private Internal Methods - // ************************************************************************ - - /** Build intercept to convert raw strings to ngStyleMap */ - protected _buildCacheInterceptor() { - let cacheInput = this._base.cacheInput.bind(this._base); - this._base.cacheInput = (key?: string, source?: any, cacheRaw = false, merge = true) => { - let styles = this._buildStyleMap(source); - if (merge) { - styles = extendObject({}, this._base.inputMap['ngStyle'], styles); - } - cacheInput(key, styles, cacheRaw); - }; + protected updateWithValue(value: any) { + const styles = this.buildStyleMap(value); + const defaultStyles = this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY, ''); + const fallback = this.buildStyleMap(defaultStyles); + this.ngStyleInstance.ngStyle = {...fallback, ...styles}; + this.ngStyleInstance.ngDoCheck(); } /** @@ -176,11 +67,10 @@ export class StyleDirective extends BaseDirective implements DoCheck, OnChanges, * Comma-delimiters are not supported due to complexities of * possible style values such as `rgba(x,x,x,x)` and others */ - protected _buildStyleMap(styles: NgStyleType) { - let sanitizer: NgStyleSanitizer = (val: any) => { - // Always safe-guard (aka sanitize) style property values - return this._sanitizer.sanitize(SecurityContext.STYLE, val) || ''; - }; + protected buildStyleMap(styles: NgStyleType): NgStyleMap { + // Always safe-guard (aka sanitize) style property values + const sanitizer: NgStyleSanitizer = (val: any) => + this.sanitizer.sanitize(SecurityContext.STYLE, val) || ''; if (styles) { switch (getType(styles)) { case 'string': return buildMapFromList(buildRawList(styles), @@ -191,22 +81,40 @@ export class StyleDirective extends BaseDirective implements DoCheck, OnChanges, } } - return styles; + return {}; } - /** Initial lookup of raw 'class' value (if any) */ - protected _fallbackToStyle() { - if (!this._base.queryInput('ngStyle')) { - this.ngStyleBase = this._getAttributeValue('style') || ''; - } + // ****************************************************************** + // Lifecycle Hooks + // ****************************************************************** + + /** For ChangeDetectionStrategy.onPush and ngOnChanges() updates */ + ngDoCheck() { + this.ngStyleInstance.ngDoCheck(); } +} - /** - * Special adapter to cross-cut responsive behaviors - * into the StyleDirective - */ - protected _base: BaseDirectiveAdapter; +const inputs = [ + 'ngStyle', + 'ngStyle.xs', 'ngStyle.sm', 'ngStyle.md', 'ngStyle.lg', 'ngStyle.xl', + 'ngStyle.lt-sm', 'ngStyle.lt-md', 'ngStyle.lt-lg', 'ngStyle.lt-xl', + 'ngStyle.gt-xs', 'ngStyle.gt-sm', 'ngStyle.gt-md', 'ngStyle.gt-lg' +]; + +const selector = ` + [ngStyle], + [ngStyle.xs], [ngStyle.sm], [ngStyle.md], [ngStyle.lg], [ngStyle.xl], + [ngStyle.lt-sm], [ngStyle.lt-md], [ngStyle.lt-lg], [ngStyle.lt-xl], + [ngStyle.gt-xs], [ngStyle.gt-sm], [ngStyle.gt-md], [ngStyle.gt-lg] +`; +/** + * Directive to add responsive support for ngStyle. + * + */ +@Directive({selector, inputs}) +export class DefaultStyleDirective extends StyleDirective implements DoCheck { + protected inputs = inputs; } /** Build a styles map from a list of styles, while sanitizing bad values first */ diff --git a/src/lib/flex/flex-align/flex-align.ts b/src/lib/flex/flex-align/flex-align.ts index a41865aa2..0050d92e0 100644 --- a/src/lib/flex/flex-align/flex-align.ts +++ b/src/lib/flex/flex-align/flex-align.ts @@ -5,20 +5,10 @@ * 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 {Directive, ElementRef, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, - Injectable, -} from '@angular/core'; -import { - BaseDirective, - MediaChange, - MediaMonitor, + MediaMarshaller, + BaseDirective2, StyleBuilder, StyleDefinition, StyleUtils, @@ -27,6 +17,7 @@ import { @Injectable({providedIn: 'root'}) export class FlexAlignStyleBuilder extends StyleBuilder { buildStyles(input: string) { + input = input || 'stretch'; const styles: StyleDefinition = {}; // Cross-axis @@ -46,87 +37,45 @@ export class FlexAlignStyleBuilder extends StyleBuilder { } } +const inputs = [ + 'fxFlexAlign', 'fxFlexAlign.xs', 'fxFlexAlign.sm', 'fxFlexAlign.md', + 'fxFlexAlign.lg', 'fxFlexAlign.xl', 'fxFlexAlign.lt-sm', 'fxFlexAlign.lt-md', + 'fxFlexAlign.lt-lg', 'fxFlexAlign.lt-xl', 'fxFlexAlign.gt-xs', 'fxFlexAlign.gt-sm', + 'fxFlexAlign.gt-md', 'fxFlexAlign.gt-lg' +]; +const selector = ` + [fxFlexAlign], [fxFlexAlign.xs], [fxFlexAlign.sm], [fxFlexAlign.md], + [fxFlexAlign.lg], [fxFlexAlign.xl], [fxFlexAlign.lt-sm], [fxFlexAlign.lt-md], + [fxFlexAlign.lt-lg], [fxFlexAlign.lt-xl], [fxFlexAlign.gt-xs], [fxFlexAlign.gt-sm], + [fxFlexAlign.gt-md], [fxFlexAlign.gt-lg] +`; + /** * '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: ` - [fxFlexAlign], - [fxFlexAlign.xs], [fxFlexAlign.sm], [fxFlexAlign.md], [fxFlexAlign.lg], [fxFlexAlign.xl], - [fxFlexAlign.lt-sm], [fxFlexAlign.lt-md], [fxFlexAlign.lt-lg], [fxFlexAlign.lt-xl], - [fxFlexAlign.gt-xs], [fxFlexAlign.gt-sm], [fxFlexAlign.gt-md], [fxFlexAlign.gt-lg] -` -}) -export class FlexAlignDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('fxFlexAlign') set align(val: string) { this._cacheInput('align', val); }; - @Input('fxFlexAlign.xs') set alignXs(val: string) { this._cacheInput('alignXs', val); }; - @Input('fxFlexAlign.sm') set alignSm(val: string) { this._cacheInput('alignSm', val); }; - @Input('fxFlexAlign.md') set alignMd(val: string) { this._cacheInput('alignMd', val); }; - @Input('fxFlexAlign.lg') set alignLg(val: string) { this._cacheInput('alignLg', val); }; - @Input('fxFlexAlign.xl') set alignXl(val: string) { this._cacheInput('alignXl', val); }; - - @Input('fxFlexAlign.lt-sm') set alignLtSm(val: string) { this._cacheInput('alignLtSm', val); }; - @Input('fxFlexAlign.lt-md') set alignLtMd(val: string) { this._cacheInput('alignLtMd', val); }; - @Input('fxFlexAlign.lt-lg') set alignLtLg(val: string) { this._cacheInput('alignLtLg', val); }; - @Input('fxFlexAlign.lt-xl') set alignLtXl(val: string) { this._cacheInput('alignLtXl', val); }; - - @Input('fxFlexAlign.gt-xs') set alignGtXs(val: string) { this._cacheInput('alignGtXs', val); }; - @Input('fxFlexAlign.gt-sm') set alignGtSm(val: string) { this._cacheInput('alignGtSm', val); }; - @Input('fxFlexAlign.gt-md') set alignGtMd(val: string) { this._cacheInput('alignGtMd', val); }; - @Input('fxFlexAlign.gt-lg') set alignGtLg(val: string) { this._cacheInput('alignGtLg', val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils, - styleBuilder: FlexAlignStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); - } - - - // ********************************************* - // Lifecycle Methods - // ********************************************* - - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes['align'] != null || this._mqActivation) { - this._updateWithValue(); - } +export class FlexAlignDirective extends BaseDirective2 { + + protected DIRECTIVE_KEY = 'flex-align'; + + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: FlexAlignStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges('align', 'stretch', (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - } - - // ********************************************* - // Protected methods - // ********************************************* - - protected _updateWithValue(value?: string|number) { - value = value || this._queryInput('align') || 'stretch'; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this.addStyles(value && (value + '') || ''); - } - - protected _styleCache = flexAlignCache; + protected styleCache = flexAlignCache; } const flexAlignCache: Map = new Map(); + +@Directive({selector, inputs}) +export class DefaultFlexAlignDirective extends FlexAlignDirective { + protected inputs = inputs; +} diff --git a/src/lib/flex/flex-fill/flex-fill.ts b/src/lib/flex/flex-fill/flex-fill.ts index 568f460fe..dd8698513 100644 --- a/src/lib/flex/flex-fill/flex-fill.ts +++ b/src/lib/flex/flex-fill/flex-fill.ts @@ -7,11 +7,11 @@ */ import {Directive, ElementRef, Injectable} from '@angular/core'; import { - BaseDirective, - MediaMonitor, + BaseDirective2, StyleBuilder, StyleDefinition, StyleUtils, + MediaMarshaller, } from '@angular/flex-layout/core'; const FLEX_FILL_CSS = { @@ -35,20 +35,17 @@ export class FlexFillStyleBuilder extends StyleBuilder { * * NOTE: fxFill is NOT responsive API!! */ -@Directive({selector: ` - [fxFill], - [fxFlexFill] -`}) -export class FlexFillDirective extends BaseDirective { - constructor(monitor: MediaMonitor, - public elRef: ElementRef, - styleUtils: StyleUtils, - styleBuilder: FlexFillStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); +@Directive({selector: `[fxFill], [fxFlexFill]`}) +export class FlexFillDirective extends BaseDirective2 { + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + protected styleBuilder: FlexFillStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); this.addStyles(''); } - protected _styleCache = flexFillCache; + protected styleCache = flexFillCache; } const flexFillCache: Map = new Map(); diff --git a/src/lib/flex/flex-offset/flex-offset.spec.ts b/src/lib/flex/flex-offset/flex-offset.spec.ts index 342fa0d4c..ee71d76b2 100644 --- a/src/lib/flex/flex-offset/flex-offset.spec.ts +++ b/src/lib/flex/flex-offset/flex-offset.spec.ts @@ -224,6 +224,7 @@ describe('flex-offset directive', () => { @Injectable({providedIn: FlexModule}) export class MockFlexOffsetStyleBuilder extends StyleBuilder { + shouldCache = false; buildStyles(_input: string) { return {'margin-top': '10px'}; } diff --git a/src/lib/flex/flex-offset/flex-offset.ts b/src/lib/flex/flex-offset/flex-offset.ts index d1a540ca8..3a79a4f62 100644 --- a/src/lib/flex/flex-offset/flex-offset.ts +++ b/src/lib/flex/flex-offset/flex-offset.ts @@ -8,27 +8,20 @@ import { Directive, ElementRef, - Input, - OnInit, OnChanges, - OnDestroy, Optional, - SimpleChanges, - SkipSelf, Injectable, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; import { - BaseDirective, - MediaChange, - MediaMonitor, + MediaMarshaller, + BaseDirective2, StyleBuilder, StyleDefinition, StyleUtils, } from '@angular/flex-layout/core'; -import {Subscription} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; -import {Layout, LayoutDirective} from '../layout/layout'; import {isFlowHorizontal} from '../../utils/layout-validator'; export interface FlexOffsetParent { @@ -39,6 +32,9 @@ export interface FlexOffsetParent { @Injectable({providedIn: 'root'}) export class FlexOffsetStyleBuilder extends StyleBuilder { buildStyles(offset: string, parent: FlexOffsetParent) { + if (offset === '') { + offset = '0'; + } const isPercent = String(offset).indexOf('%') > -1; const isPx = String(offset).indexOf('px') > -1; if (!isPx && !isPercent && !isNaN(+offset)) { @@ -52,153 +48,74 @@ export class FlexOffsetStyleBuilder extends StyleBuilder { } } +const inputs = [ + 'fxFlexOffset', 'fxFlexOffset.xs', 'fxFlexOffset.sm', 'fxFlexOffset.md', + 'fxFlexOffset.lg', 'fxFlexOffset.xl', 'fxFlexOffset.lt-sm', 'fxFlexOffset.lt-md', + 'fxFlexOffset.lt-lg', 'fxFlexOffset.lt-xl', 'fxFlexOffset.gt-xs', 'fxFlexOffset.gt-sm', + 'fxFlexOffset.gt-md', 'fxFlexOffset.gt-lg' +]; +const selector = ` + [fxFlexOffset], [fxFlexOffset.xs], [fxFlexOffset.sm], [fxFlexOffset.md], + [fxFlexOffset.lg], [fxFlexOffset.xl], [fxFlexOffset.lt-sm], [fxFlexOffset.lt-md], + [fxFlexOffset.lt-lg], [fxFlexOffset.lt-xl], [fxFlexOffset.gt-xs], [fxFlexOffset.gt-sm], + [fxFlexOffset.gt-md], [fxFlexOffset.gt-lg] +`; + /** * 'flex-offset' flexbox styling directive * Configures the 'margin-left' of the element in a layout container */ -@Directive({selector: ` - [fxFlexOffset], - [fxFlexOffset.xs], [fxFlexOffset.sm], [fxFlexOffset.md], [fxFlexOffset.lg], [fxFlexOffset.xl], - [fxFlexOffset.lt-sm], [fxFlexOffset.lt-md], [fxFlexOffset.lt-lg], [fxFlexOffset.lt-xl], - [fxFlexOffset.gt-xs], [fxFlexOffset.gt-sm], [fxFlexOffset.gt-md], [fxFlexOffset.gt-lg] -`}) -export class FlexOffsetDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - private _directionWatcher: Subscription; - - /* tslint:disable */ - @Input('fxFlexOffset') set offset(val: string) { this._cacheInput('offset', val); } - @Input('fxFlexOffset.xs') set offsetXs(val: string) { this._cacheInput('offsetXs', val); } - @Input('fxFlexOffset.sm') set offsetSm(val: string) { this._cacheInput('offsetSm', val); }; - @Input('fxFlexOffset.md') set offsetMd(val: string) { this._cacheInput('offsetMd', val); }; - @Input('fxFlexOffset.lg') set offsetLg(val: string) { this._cacheInput('offsetLg', val); }; - @Input('fxFlexOffset.xl') set offsetXl(val: string) { this._cacheInput('offsetXl', val); }; - - @Input('fxFlexOffset.lt-sm') set offsetLtSm(val: string) { this._cacheInput('offsetLtSm', val); }; - @Input('fxFlexOffset.lt-md') set offsetLtMd(val: string) { this._cacheInput('offsetLtMd', val); }; - @Input('fxFlexOffset.lt-lg') set offsetLtLg(val: string) { this._cacheInput('offsetLtLg', val); }; - @Input('fxFlexOffset.lt-xl') set offsetLtXl(val: string) { this._cacheInput('offsetLtXl', val); }; - - @Input('fxFlexOffset.gt-xs') set offsetGtXs(val: string) { this._cacheInput('offsetGtXs', val); }; - @Input('fxFlexOffset.gt-sm') set offsetGtSm(val: string) { this._cacheInput('offsetGtSm', val); }; - @Input('fxFlexOffset.gt-md') set offsetGtMd(val: string) { this._cacheInput('offsetGtMd', val); }; - @Input('fxFlexOffset.gt-lg') set offsetGtLg(val: string) { this._cacheInput('offsetGtLg', val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - @Optional() @SkipSelf() protected _container: LayoutDirective, - private _directionality: Directionality, - styleUtils: StyleUtils, - styleBuilder: FlexOffsetStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); - - this._directionWatcher = - this._directionality.change.subscribe(this._updateWithValue.bind(this)); - - this.watchParentFlow(); - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* - - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes['offset'] != null || this._mqActivation) { - this._updateWithValue(); +export class FlexOffsetDirective extends BaseDirective2 implements OnChanges { + protected DIRECTIVE_KEY = 'flex-offset'; + + constructor(protected elRef: ElementRef, + protected directionality: Directionality, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: FlexOffsetStyleBuilder, + protected marshal: MediaMarshaller, + protected styler: StyleUtils) { + super(elRef, styleBuilder, styler, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this), [this.directionality.change]); + if (this.parentElement) { + this.marshal.trackValue(this.parentElement, 'layout-gap') + .pipe(takeUntil(this.destroySubject)) + .subscribe(this.triggerUpdate.bind(this)); } } - /** - * Cleanup - */ - ngOnDestroy() { - super.ngOnDestroy(); - if (this._layoutWatcher) { - this._layoutWatcher.unsubscribe(); - } - if (this._directionWatcher) { - this._directionWatcher.unsubscribe(); - } - } - - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges('offset', 0 , (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - } - // ********************************************* // Protected methods // ********************************************* - /** The flex-direction of this element's host container. Defaults to 'row'. */ - protected _layout = {direction: 'row', wrap: false}; - - /** - * Subscription to the parent flex container's layout changes. - * Stored so we can unsubscribe when this directive is destroyed. - */ - protected _layoutWatcher?: Subscription; - - /** - * If parent flow-direction changes, then update the margin property - * used to offset - */ - protected watchParentFlow() { - if (this._container) { - // Subscribe to layout immediate parent direction changes (if any) - this._layoutWatcher = this._container.layout$.subscribe((layout) => { - // `direction` === null if parent container does not have a `fxLayout` - this._onLayoutChange(layout); - }); - } - } - - /** - * 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(layout?: Layout) { - this._layout = layout || this._layout || {direction: 'row', wrap: false}; - this._updateWithValue(); - } - /** * Using the current fxFlexOffset value, update the inline CSS * NOTE: this will assign `margin-left` if the parent flex-direction == 'row', * otherwise `margin-top` is used for the offset. */ - protected _updateWithValue(value?: string|number) { - value = value || this._queryInput('offset') || 0; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - + protected updateWithValue(value: string|number = ''): void { // The flex-direction of this element's flex container. Defaults to 'row'. - const layout = this._getFlexFlowDirection(this.parentElement, true); - const isRtl = this._directionality.value === 'rtl'; + const layout = this.getFlexFlowDirection(this.parentElement!, true); + const isRtl = this.directionality.value === 'rtl'; if (layout === 'row' && isRtl) { - this._styleCache = flexOffsetCacheRowRtl; + this.styleCache = flexOffsetCacheRowRtl; } else if (layout === 'row' && !isRtl) { - this._styleCache = flexOffsetCacheRowLtr; + this.styleCache = flexOffsetCacheRowLtr; } else if (layout === 'column' && isRtl) { - this._styleCache = flexOffsetCacheColumnRtl; + this.styleCache = flexOffsetCacheColumnRtl; } else if (layout === 'column' && !isRtl) { - this._styleCache = flexOffsetCacheColumnLtr; + this.styleCache = flexOffsetCacheColumnLtr; } - this.addStyles((value && (value + '') || ''), {layout, isRtl}); + this.addStyles(value + '', {layout, isRtl}); } } +@Directive({selector, inputs}) +export class DefaultFlexOffsetDirective extends FlexOffsetDirective { + protected inputs = inputs; +} + const flexOffsetCacheRowRtl: Map = new Map(); const flexOffsetCacheColumnRtl: Map = new Map(); const flexOffsetCacheRowLtr: Map = new Map(); diff --git a/src/lib/flex/flex-order/flex-order.ts b/src/lib/flex/flex-order/flex-order.ts index 644a8c784..0a7c8a4cd 100644 --- a/src/lib/flex/flex-order/flex-order.ts +++ b/src/lib/flex/flex-order/flex-order.ts @@ -5,112 +5,62 @@ * 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 {Directive, ElementRef, OnChanges, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, - Injectable, -} from '@angular/core'; -import { - BaseDirective, - MediaChange, - MediaMonitor, + BaseDirective2, StyleBuilder, StyleDefinition, StyleUtils, + MediaMarshaller, } from '@angular/flex-layout/core'; @Injectable({providedIn: 'root'}) export class FlexOrderStyleBuilder extends StyleBuilder { buildStyles(value: string) { - const val = parseInt(value, 10); - const styles = {order: isNaN(val) ? 0 : val}; - return styles; + const val = parseInt((value || '0'), 10); + return {order: isNaN(val) ? 0 : val}; } } +const inputs = [ + 'fxFlexOrder', 'fxFlexOrder.xs', 'fxFlexOrder.sm', 'fxFlexOrder.md', + 'fxFlexOrder.lg', 'fxFlexOrder.xl', 'fxFlexOrder.lt-sm', 'fxFlexOrder.lt-md', + 'fxFlexOrder.lt-lg', 'fxFlexOrder.lt-xl', 'fxFlexOrder.gt-xs', 'fxFlexOrder.gt-sm', + 'fxFlexOrder.gt-md', 'fxFlexOrder.gt-lg' +]; +const selector = ` + [fxFlexOrder], [fxFlexOrder.xs], [fxFlexOrder.sm], [fxFlexOrder.md], + [fxFlexOrder.lg], [fxFlexOrder.xl], [fxFlexOrder.lt-sm], [fxFlexOrder.lt-md], + [fxFlexOrder.lt-lg], [fxFlexOrder.lt-xl], [fxFlexOrder.gt-xs], [fxFlexOrder.gt-sm], + [fxFlexOrder.gt-md], [fxFlexOrder.gt-lg] +`; + /** * '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: ` - [fxFlexOrder], - [fxFlexOrder.xs], [fxFlexOrder.sm], [fxFlexOrder.md], [fxFlexOrder.lg], [fxFlexOrder.xl], - [fxFlexOrder.lt-sm], [fxFlexOrder.lt-md], [fxFlexOrder.lt-lg], [fxFlexOrder.lt-xl], - [fxFlexOrder.gt-xs], [fxFlexOrder.gt-sm], [fxFlexOrder.gt-md], [fxFlexOrder.gt-lg] -`}) -export class FlexOrderDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('fxFlexOrder') set order(val: string) { this._cacheInput('order', val); } - @Input('fxFlexOrder.xs') set orderXs(val: string) { this._cacheInput('orderXs', val); } - @Input('fxFlexOrder.sm') set orderSm(val: string) { this._cacheInput('orderSm', val); }; - @Input('fxFlexOrder.md') set orderMd(val: string) { this._cacheInput('orderMd', val); }; - @Input('fxFlexOrder.lg') set orderLg(val: string) { this._cacheInput('orderLg', val); }; - @Input('fxFlexOrder.xl') set orderXl(val: string) { this._cacheInput('orderXl', val); }; - - @Input('fxFlexOrder.gt-xs') set orderGtXs(val: string) { this._cacheInput('orderGtXs', val); }; - @Input('fxFlexOrder.gt-sm') set orderGtSm(val: string) { this._cacheInput('orderGtSm', val); }; - @Input('fxFlexOrder.gt-md') set orderGtMd(val: string) { this._cacheInput('orderGtMd', val); }; - @Input('fxFlexOrder.gt-lg') set orderGtLg(val: string) { this._cacheInput('orderGtLg', val); }; - - @Input('fxFlexOrder.lt-sm') set orderLtSm(val: string) { this._cacheInput('orderLtSm', val); }; - @Input('fxFlexOrder.lt-md') set orderLtMd(val: string) { this._cacheInput('orderLtMd', val); }; - @Input('fxFlexOrder.lt-lg') set orderLtLg(val: string) { this._cacheInput('orderLtLg', val); }; - @Input('fxFlexOrder.lt-xl') set orderLtXl(val: string) { this._cacheInput('orderLtXl', val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils, - styleBuilder: FlexOrderStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* +export class FlexOrderDirective extends BaseDirective2 implements OnChanges { - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes['order'] != null || this._mqActivation) { - this._updateWithValue(); - } - } - - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges('order', '0', (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - } + protected DIRECTIVE_KEY = 'flex-order'; - // ********************************************* - // Protected methods - // ********************************************* - - protected _updateWithValue(value?: string) { - value = value || this._queryInput('order') || '0'; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this.addStyles(value || ''); + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: FlexOrderStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - protected _styleCache = flexOrderCache; + protected styleCache = flexOrderCache; } const flexOrderCache: Map = new Map(); + +@Directive({selector, inputs}) +export class DefaultFlexOrderDirective extends FlexOrderDirective { + protected inputs = inputs; +} diff --git a/src/lib/flex/flex/flex.spec.ts b/src/lib/flex/flex/flex.spec.ts index e47747934..bb1359e10 100644 --- a/src/lib/flex/flex/flex.spec.ts +++ b/src/lib/flex/flex/flex.spec.ts @@ -18,8 +18,8 @@ import { } from '@angular/flex-layout/core'; import {FlexLayoutModule} from '../../module'; -import {FlexDirective, FlexStyleBuilder} from './flex'; -import {LayoutDirective} from '../layout/layout'; +import {DefaultFlexDirective, FlexStyleBuilder} from './flex'; +import {DefaultLayoutDirective} from '../layout/layout'; import {customMatchers, expect} from '../../utils/testing/custom-matchers'; import { makeCreateTestComponent, @@ -103,6 +103,77 @@ describe('flex directive', () => { expectEl(element).toHaveStyle({'flex': '10 1 auto'}, styler); }); + it('should add correct styles for `fxFlex` with multiple layout changes and wraps', () => { + componentWithTemplate(` +
+
+
+ `); + fixture.detectChanges(); + let element = queryFor(fixture, '[fxFlex]')[0]; + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'column'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-height': '30%'}, styler); + + fixture.debugElement.componentInstance.direction = 'row'; + fixture.detectChanges(); + + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'row'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-width': '30%'}, styler); + + fixture.debugElement.componentInstance.direction = 'column'; + fixture.detectChanges(); + + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'column'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-height': '30%'}, styler); + }); + + it('should add correct styles for `fxFlex` with gap in grid mode and wrap parent', () => { + componentWithTemplate(` +
+
+
+
+
+ `); + fixture.debugElement.componentInstance.direction = 'row'; + fixture.detectChanges(); + let element = queryFor(fixture, '[fxFlex]')[0]; + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'row'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-width': '30%'}, styler); + + fixture.debugElement.componentInstance.direction = 'row-reverse'; + fixture.detectChanges(); + + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'row-reverse'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-width': '30%'}, styler); + + fixture.debugElement.componentInstance.direction = 'column'; + fixture.detectChanges(); + + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'column'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-height': '30%'}, styler); + + fixture.debugElement.componentInstance.direction = 'column-reverse'; + fixture.detectChanges(); + + expectNativeEl(fixture).toHaveStyle({'flex-direction': 'column-reverse'}, styler); + expectEl(element).toHaveStyle({'box-sizing': 'border-box'}, styler); + expectEl(element).toHaveStyle({'flex': '1 1 30%'}, styler); + expectEl(element).toHaveStyle({'max-height': '30%'}, styler); + }); + it('should add correct styles for `fxFlex` and ngStyle with multiple layout changes', () => { // NOTE: the presence of ngIf on the child element is imperative for detecting 700 componentWithTemplate(` @@ -699,7 +770,7 @@ describe('flex directive', () => { fixture = TestBed.createComponent(TestQueryWithFlexComponent); fixture.detectChanges(); - const layout: LayoutDirective = fixture.debugElement.componentInstance.layout; + const layout: DefaultLayoutDirective = fixture.debugElement.componentInstance.layout; expect(layout).toBeDefined(); expect(layout.activatedValue).toBe(''); @@ -714,13 +785,12 @@ describe('flex directive', () => { }, _styler); }) ); - it('should query the ViewChild `fxFlex` directive properly', inject([StyleUtils], (_styler: StyleUtils) => { fixture = TestBed.createComponent(TestQueryWithFlexComponent); fixture.detectChanges(); - const flex: FlexDirective = fixture.debugElement.componentInstance.flex; + const flex: DefaultFlexDirective = fixture.debugElement.componentInstance.flex; // Test for percentage value assignments expect(flex).toBeDefined(); @@ -747,14 +817,13 @@ describe('flex directive', () => { expectEl(nodes[0]).toHaveStyle({'max-width': '27.5px'}, _styler); }) ); - it('should restore `fxFlex` value after breakpoint activations', inject([MatchMedia, StyleUtils], (_matchMedia: MockMatchMedia, _styler: StyleUtils) => { fixture = TestBed.createComponent(TestQueryWithFlexComponent); fixture.detectChanges(); - const flex: FlexDirective = fixture.debugElement.componentInstance.flex; + const flex: DefaultFlexDirective = fixture.debugElement.componentInstance.flex; // Test for raw value assignments that are converted to percentages expect(flex).toBeDefined(); @@ -940,6 +1009,7 @@ describe('flex directive', () => { @Injectable() export class MockFlexStyleBuilder extends StyleBuilder { + shouldCache = false; buildStyles(_input: string) { return {'flex': '1 1 30%'}; } @@ -967,6 +1037,6 @@ class TestFlexComponent { ` }) class TestQueryWithFlexComponent { - @ViewChild(FlexDirective) flex!: FlexDirective; - @ViewChild(LayoutDirective) layout!: LayoutDirective; + @ViewChild(DefaultFlexDirective) flex!: DefaultFlexDirective; + @ViewChild(DefaultLayoutDirective) layout!: DefaultLayoutDirective; } diff --git a/src/lib/flex/flex/flex.ts b/src/lib/flex/flex/flex.ts index 3d9c5735f..6577599ad 100644 --- a/src/lib/flex/flex/flex.ts +++ b/src/lib/flex/flex/flex.ts @@ -5,39 +5,23 @@ * 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 {Directive, ElementRef, Inject, Injectable, Input} from '@angular/core'; import { - Directive, - ElementRef, - Inject, - Injectable, - Input, - OnChanges, - OnDestroy, - OnInit, - Optional, - SimpleChanges, - SkipSelf, -} from '@angular/core'; -import { - BaseDirective, + BaseDirective2, LayoutConfigOptions, LAYOUT_CONFIG, - MediaChange, - MediaMonitor, StyleUtils, validateBasis, StyleBuilder, StyleDefinition, + MediaMarshaller, + ElementMatcher, } from '@angular/flex-layout/core'; -import {Subscription} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; import {extendObject} from '../../utils/object-extend'; -import {Layout, LayoutDirective} from '../layout/layout'; import {isFlowHorizontal} from '../../utils/layout-validator'; -/** Built-in aliases for different flex-basis values. */ -export type FlexBasisAlias = 'grow' | 'initial' | 'auto' | 'none' | 'nogrow' | 'noshrink'; - interface FlexBuilderParent { direction: string; hasWrap: boolean; @@ -196,102 +180,61 @@ export class FlexStyleBuilder extends StyleBuilder { } } +const inputs = [ + 'fxFlex', 'fxFlex.xs', 'fxFlex.sm', 'fxFlex.md', + 'fxFlex.lg', 'fxFlex.xl', 'fxFlex.lt-sm', 'fxFlex.lt-md', + 'fxFlex.lt-lg', 'fxFlex.lt-xl', 'fxFlex.gt-xs', 'fxFlex.gt-sm', + 'fxFlex.gt-md', 'fxFlex.gt-lg' +]; +const selector = ` + [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], + [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], + [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], + [fxFlex.gt-md], [fxFlex.gt-lg] +`; + /** * Directive to control the size of a flex item using flex-basis, flex-grow, and flex-shrink. * Corresponds to the css `flex` shorthand property. * * @see https://css-tricks.com/snippets/css/a-guide-to-flexbox/ */ -@Directive({ - selector: ` - [fxFlex], - [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], - [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], - [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg], - `, -}) -export class FlexDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /** The flex-direction of this element's flex container. Defaults to 'row'. */ - protected _layout?: Layout; +export class FlexDirective extends BaseDirective2 { - /** - * 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: string) { this._cacheInput('shrink', val); }; - @Input('fxGrow') set grow(val: string) { this._cacheInput('grow', val); }; - - @Input('fxFlex') set flex(val: string) { this._cacheInput('flex', val); }; - @Input('fxFlex.xs') set flexXs(val: string) { this._cacheInput('flexXs', val); }; - @Input('fxFlex.sm') set flexSm(val: string) { this._cacheInput('flexSm', val); }; - @Input('fxFlex.md') set flexMd(val: string) { this._cacheInput('flexMd', val); }; - @Input('fxFlex.lg') set flexLg(val: string) { this._cacheInput('flexLg', val); }; - @Input('fxFlex.xl') set flexXl(val: string) { this._cacheInput('flexXl', val); }; - - @Input('fxFlex.gt-xs') set flexGtXs(val: string) { this._cacheInput('flexGtXs', val); }; - @Input('fxFlex.gt-sm') set flexGtSm(val: string) { this._cacheInput('flexGtSm', val); }; - @Input('fxFlex.gt-md') set flexGtMd(val: string) { this._cacheInput('flexGtMd', val); }; - @Input('fxFlex.gt-lg') set flexGtLg(val: string) { this._cacheInput('flexGtLg', val); }; - - @Input('fxFlex.lt-sm') set flexLtSm(val: string) { this._cacheInput('flexLtSm', val); }; - @Input('fxFlex.lt-md') set flexLtMd(val: string) { this._cacheInput('flexLtMd', val); }; - @Input('fxFlex.lt-lg') set flexLtLg(val: string) { this._cacheInput('flexLtLg', val); }; - @Input('fxFlex.lt-xl') set flexLtXl(val: string) { this._cacheInput('flexLtXl', val); }; - /* tslint:enable */ - - // Note: Explicitly @SkipSelf on LayoutDirective because we are looking - // for the parent flex container for this flex item. - constructor(monitor: MediaMonitor, - elRef: ElementRef, - @Optional() @SkipSelf() protected _container: LayoutDirective, - protected styleUtils: StyleUtils, - @Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions, - protected styleBuilder: FlexStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); + protected DIRECTIVE_KEY = 'flex'; + protected direction = ''; + protected wrap = false; - this._cacheInput('flex', ''); - this._cacheInput('shrink', 1); - this._cacheInput('grow', 1); - } - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes['flex'] != null || this._mqActivation) { - this._updateStyle(); - } + @Input('fxShrink') + get shrink(): string { return this.flexShrink; } + set shrink(value: string) { + this.flexShrink = value || '1'; + this.triggerReflow(); } - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges('flex', '', (changes: MediaChange) => { - this._updateStyle(changes.value); - }); - - if (this._container) { - // If this flex item is inside of a flex container marked with - // Subscribe to layout immediate parent direction changes - this._layoutWatcher = this._container.layout$.subscribe((layout) => { - // `direction` === null if parent container does not have a `fxLayout` - this._onLayoutChange(layout); - }); - } + @Input('fxGrow') + get grow(): string { return this.flexGrow; } + set grow(value: string) { + this.flexGrow = value || '1'; + this.triggerReflow(); } - ngOnDestroy() { - super.ngOnDestroy(); - if (this._layoutWatcher) { - this._layoutWatcher.unsubscribe(); + protected flexGrow = '1'; + protected flexShrink = '1'; + + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + @Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions, + protected styleBuilder: FlexStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.updateStyle.bind(this)); + if (this.parentElement) { + this.marshal.trackValue(this.parentElement, 'layout') + .pipe(takeUntil(this.destroySubject)) + .subscribe(this.onLayoutChange.bind(this)); } } @@ -299,33 +242,47 @@ export class FlexDirective extends BaseDirective implements OnInit, OnChanges, O * 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(layout?: Layout) { - this._layout = layout || this._layout || {direction: 'row', wrap: false}; - this._updateStyle(); + protected onLayoutChange(matcher: ElementMatcher) { + const layout: string = matcher.value; + const layoutParts = layout.split(' '); + this.direction = layoutParts[0]; + this.wrap = layoutParts[1] !== undefined && layoutParts[1] === 'wrap'; + this.triggerUpdate(); } - protected _updateStyle(value?: string|number) { - let flexBasis = value || this._queryInput('flex') || ''; - if (this._mqActivation) { - flexBasis = this._mqActivation.activatedInput; - } - - const basis = String(flexBasis).replace(';', ''); - const parts = validateBasis(basis, this._queryInput('grow'), this._queryInput('shrink')); + /** Input to this is exclusively the basis input value */ + protected updateStyle(value: string) { const addFlexToParent = this.layoutConfig.addFlexToParent !== false; - const direction = this._getFlexFlowDirection(this.parentElement, addFlexToParent); - const hasWrap = this._layout && this._layout.wrap; - if (direction === 'row' && hasWrap) { - this._styleCache = flexRowWrapCache; - } else if (direction === 'row' && !hasWrap) { - this._styleCache = flexRowCache; - } else if (direction === 'column' && hasWrap) { - this._styleCache = flexColumnWrapCache; - } else if (direction === 'column' && !hasWrap) { - this._styleCache = flexColumnCache; + if (!this.direction) { + this.direction = this.getFlexFlowDirection(this.parentElement!, addFlexToParent); + } + const direction = this.direction; + const isHorizontal = direction.startsWith('row'); + const hasWrap = this.wrap; + if (isHorizontal && hasWrap) { + this.styleCache = flexRowWrapCache; + } else if (isHorizontal && !hasWrap) { + this.styleCache = flexRowCache; + } else if (!isHorizontal && hasWrap) { + this.styleCache = flexColumnWrapCache; + } else if (!isHorizontal && !hasWrap) { + this.styleCache = flexColumnCache; } + const basis = String(value).replace(';', ''); + const parts = validateBasis(basis, this.flexGrow, this.flexShrink); this.addStyles(parts.join(' '), {direction, hasWrap}); } + + /** Trigger a style reflow, usually based on a shrink/grow input event */ + protected triggerReflow() { + const parts = validateBasis(this.activatedValue, this.flexGrow, this.flexShrink); + this.marshal.updateElement(this.nativeElement, this.DIRECTIVE_KEY, parts.join(' ')); + } +} + +@Directive({inputs, selector}) +export class DefaultFlexDirective extends FlexDirective { + protected inputs = inputs; } const flexRowCache: Map = new Map(); diff --git a/src/lib/flex/layout-align/layout-align.ts b/src/lib/flex/layout-align/layout-align.ts index cc3cac287..850d9e0ca 100644 --- a/src/lib/flex/layout-align/layout-align.ts +++ b/src/lib/flex/layout-align/layout-align.ts @@ -5,30 +5,18 @@ * 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 {Directive, ElementRef, Optional, Injectable} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnChanges, - OnDestroy, - OnInit, - Optional, - SimpleChanges, - Self, - Injectable, -} from '@angular/core'; -import { - BaseDirective, - MediaChange, - MediaMonitor, + BaseDirective2, StyleBuilder, StyleDefinition, StyleUtils, + MediaMarshaller, + ElementMatcher, } from '@angular/flex-layout/core'; -import {Subscription} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; import {extendObject} from '../../utils/object-extend'; -import {Layout, LayoutDirective} from '../layout/layout'; import {LAYOUT_VALUES, isFlowHorizontal} from '../../utils/layout-validator'; export interface LayoutAlignParent { @@ -108,6 +96,19 @@ export class LayoutAlignStyleBuilder extends StyleBuilder { } } +const inputs = [ + 'fxLayoutAlign', 'fxLayoutAlign.xs', 'fxLayoutAlign.sm', 'fxLayoutAlign.md', + 'fxLayoutAlign.lg', 'fxLayoutAlign.xl', 'fxLayoutAlign.lt-sm', 'fxLayoutAlign.lt-md', + 'fxLayoutAlign.lt-lg', 'fxLayoutAlign.lt-xl', 'fxLayoutAlign.gt-xs', 'fxLayoutAlign.gt-sm', + 'fxLayoutAlign.gt-md', 'fxLayoutAlign.gt-lg' +]; +const selector = ` + [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], + [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], + [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], + [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg] +`; + /** * 'layout-align' flexbox styling directive * Defines positioning of child elements along main and cross axis in a layout container @@ -117,76 +118,22 @@ export class LayoutAlignStyleBuilder extends StyleBuilder { * @see https://css-tricks.com/almanac/properties/a/align-items/ * @see https://css-tricks.com/almanac/properties/a/align-content/ */ -@Directive({selector: ` - [fxLayoutAlign], - [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg],[fxLayoutAlign.xl], - [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], - [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg] -`}) -export class LayoutAlignDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - protected _layout = 'row'; // default flex-direction - protected _layoutWatcher?: Subscription; - - /* tslint:disable */ - @Input('fxLayoutAlign') set align(val: string) { this._cacheInput('align', val); } - @Input('fxLayoutAlign.xs') set alignXs(val: string) { this._cacheInput('alignXs', val); } - @Input('fxLayoutAlign.sm') set alignSm(val: string) { this._cacheInput('alignSm', val); }; - @Input('fxLayoutAlign.md') set alignMd(val: string) { this._cacheInput('alignMd', val); }; - @Input('fxLayoutAlign.lg') set alignLg(val: string) { this._cacheInput('alignLg', val); }; - @Input('fxLayoutAlign.xl') set alignXl(val: string) { this._cacheInput('alignXl', val); }; - - @Input('fxLayoutAlign.gt-xs') set alignGtXs(val: string) { this._cacheInput('alignGtXs', val); }; - @Input('fxLayoutAlign.gt-sm') set alignGtSm(val: string) { this._cacheInput('alignGtSm', val); }; - @Input('fxLayoutAlign.gt-md') set alignGtMd(val: string) { this._cacheInput('alignGtMd', val); }; - @Input('fxLayoutAlign.gt-lg') set alignGtLg(val: string) { this._cacheInput('alignGtLg', val); }; - - @Input('fxLayoutAlign.lt-sm') set alignLtSm(val: string) { this._cacheInput('alignLtSm', val); }; - @Input('fxLayoutAlign.lt-md') set alignLtMd(val: string) { this._cacheInput('alignLtMd', val); }; - @Input('fxLayoutAlign.lt-lg') set alignLtLg(val: string) { this._cacheInput('alignLtLg', val); }; - @Input('fxLayoutAlign.lt-xl') set alignLtXl(val: string) { this._cacheInput('alignLtXl', val); }; - - /* tslint:enable */ - constructor( - monitor: MediaMonitor, - elRef: ElementRef, - @Optional() @Self() container: LayoutDirective, - styleUtils: StyleUtils, - styleBuilder: LayoutAlignStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); - - if (container) { // Subscribe to layout direction changes - this._layoutWatcher = container.layout$.subscribe(this._onLayoutChange.bind(this)); - } - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* - - ngOnChanges(changes: SimpleChanges) { - if (changes['align'] != null || this._mqActivation) { - this._updateWithValue(); - } - } - - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges('align', 'start stretch', (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - } - - ngOnDestroy() { - super.ngOnDestroy(); - if ( this._layoutWatcher ) { - this._layoutWatcher.unsubscribe(); - } +export class LayoutAlignDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'layout-align'; + protected layout = 'row'; // default flex-direction + + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: LayoutAlignStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); + this.marshal.trackValue(this.nativeElement, 'layout') + .pipe(takeUntil(this.destroySubject)) + .subscribe(this.onLayoutChange.bind(this)); } // ********************************************* @@ -196,34 +143,39 @@ export class LayoutAlignDirective extends BaseDirective implements OnInit, OnCha /** * */ - protected _updateWithValue(value?: string) { - value = value || this._queryInput('align') || 'start stretch'; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; + protected updateWithValue(value: string) { + const layout = this.layout || 'row'; + if (layout === 'row') { + this.styleCache = layoutAlignHorizontalCache; + } else if (layout === 'row-reverse') { + this.styleCache = layoutAlignHorizontalRevCache; + } else if (layout === 'column') { + this.styleCache = layoutAlignVerticalCache; + } else if (layout === 'column-reverse') { + this.styleCache = layoutAlignVerticalRevCache; } - - const layout = this._layout || 'row'; - this._styleCache = layout === 'row' ? - layoutAlignHorizontalCache : layoutAlignVerticalCache; - this.addStyles(value || '', {layout}); + this.addStyles(value, {layout}); } /** * Cache the parent container 'flex-direction' and update the 'flex' styles */ - protected _onLayoutChange(layout: Layout) { - this._layout = (layout.direction || '').toLowerCase(); - if (!LAYOUT_VALUES.find(x => x === this._layout)) { - this._layout = 'row'; + protected onLayoutChange(matcher: ElementMatcher) { + const layout: string = matcher.value; + this.layout = layout.split(' ')[0]; + if (!LAYOUT_VALUES.find(x => x === this.layout)) { + this.layout = 'row'; } - - let value = this._queryInput('align') || 'start stretch'; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - this.addStyles(value, {layout: this._layout || 'row'}); + this.triggerUpdate(); } } +@Directive({selector, inputs}) +export class DefaultLayoutAlignDirective extends LayoutAlignDirective { + protected inputs = inputs; +} + const layoutAlignHorizontalCache: Map = new Map(); const layoutAlignVerticalCache: Map = new Map(); +const layoutAlignHorizontalRevCache: Map = new Map(); +const layoutAlignVerticalRevCache: Map = new Map(); diff --git a/src/lib/flex/layout-gap/layout-gap.ts b/src/lib/flex/layout-gap/layout-gap.ts index e1c6ffb54..c7dc13ac5 100644 --- a/src/lib/flex/layout-gap/layout-gap.ts +++ b/src/lib/flex/layout-gap/layout-gap.ts @@ -8,28 +8,24 @@ import { Directive, ElementRef, - Input, - OnChanges, - SimpleChanges, - Self, - AfterContentInit, Optional, OnDestroy, NgZone, Injectable, + AfterContentInit, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; import { - BaseDirective, - MediaChange, - MediaMonitor, + BaseDirective2, StyleBuilder, StyleDefinition, - StyleUtils + StyleUtils, + MediaMarshaller, + ElementMatcher, } from '@angular/flex-layout/core'; -import {Subscription} from 'rxjs'; +import {Subject} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; -import {Layout, LayoutDirective} from '../layout/layout'; import {LAYOUT_VALUES} from '../../utils/layout-validator'; export interface LayoutGapParent { @@ -83,92 +79,70 @@ export class LayoutGapStyleBuilder extends StyleBuilder { } } +const inputs = [ + 'fxLayoutGap', 'fxLayoutGap.xs', 'fxLayoutGap.sm', 'fxLayoutGap.md', + 'fxLayoutGap.lg', 'fxLayoutGap.xl', 'fxLayoutGap.lt-sm', 'fxLayoutGap.lt-md', + 'fxLayoutGap.lt-lg', 'fxLayoutGap.lt-xl', 'fxLayoutGap.gt-xs', 'fxLayoutGap.gt-sm', + 'fxLayoutGap.gt-md', 'fxLayoutGap.gt-lg' +]; +const selector = ` + [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], + [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], + [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], + [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg] +`; + /** * 'layout-padding' styling directive * Defines padding of child elements in a layout container */ -@Directive({ - selector: ` - [fxLayoutGap], - [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], - [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], - [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg] -` -}) -export class LayoutGapDirective extends BaseDirective - implements AfterContentInit, OnChanges, OnDestroy { - protected _layout = 'row'; // default flex-direction - protected _layoutWatcher?: Subscription; - protected _observer?: MutationObserver; - private readonly _directionWatcher: Subscription; - - /* tslint:disable */ - @Input('fxLayoutGap') set gap(val: string) { this._cacheInput('gap', val); } - @Input('fxLayoutGap.xs') set gapXs(val: string) { this._cacheInput('gapXs', val); } - @Input('fxLayoutGap.sm') set gapSm(val: string) { this._cacheInput('gapSm', val); }; - @Input('fxLayoutGap.md') set gapMd(val: string) { this._cacheInput('gapMd', val); }; - @Input('fxLayoutGap.lg') set gapLg(val: string) { this._cacheInput('gapLg', val); }; - @Input('fxLayoutGap.xl') set gapXl(val: string) { this._cacheInput('gapXl', val); }; - - @Input('fxLayoutGap.gt-xs') set gapGtXs(val: string) { this._cacheInput('gapGtXs', val); }; - @Input('fxLayoutGap.gt-sm') set gapGtSm(val: string) { this._cacheInput('gapGtSm', val); }; - @Input('fxLayoutGap.gt-md') set gapGtMd(val: string) { this._cacheInput('gapGtMd', val); }; - @Input('fxLayoutGap.gt-lg') set gapGtLg(val: string) { this._cacheInput('gapGtLg', val); }; - - @Input('fxLayoutGap.lt-sm') set gapLtSm(val: string) { this._cacheInput('gapLtSm', val); }; - @Input('fxLayoutGap.lt-md') set gapLtMd(val: string) { this._cacheInput('gapLtMd', val); }; - @Input('fxLayoutGap.lt-lg') set gapLtLg(val: string) { this._cacheInput('gapLtLg', val); }; - @Input('fxLayoutGap.lt-xl') set gapLtXl(val: string) { this._cacheInput('gapLtXl', val); }; - - /* tslint:enable */ - constructor(protected monitor: MediaMonitor, - protected elRef: ElementRef, - @Optional() @Self() protected container: LayoutDirective, - protected _zone: NgZone, - protected _directionality: Directionality, - protected styleUtils: StyleUtils, - protected styleBuilder: LayoutGapStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); - - if (container) { // Subscribe to layout direction changes - this._layoutWatcher = container.layout$.subscribe(this._onLayoutChange.bind(this)); +export class LayoutGapDirective extends BaseDirective2 implements AfterContentInit, OnDestroy { + protected layout = 'row'; // default flex-direction + protected DIRECTIVE_KEY = 'layout-gap'; + protected observerSubject = new Subject(); + + /** Special accessor to query for all child 'element' nodes regardless of type, class, etc */ + protected get childrenNodes(): HTMLElement[] { + const obj = this.nativeElement.children; + const buffer: any[] = []; + + // iterate backwards ensuring that length is an UInt32 + for (let i = obj.length; i--; ) { + buffer[i] = obj[i]; } - this._directionWatcher = - this._directionality.change.subscribe(this._updateWithValue.bind(this)); + return buffer; + } + + constructor(protected elRef: ElementRef, + protected zone: NgZone, + protected directionality: Directionality, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: LayoutGapStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this), [this.directionality.change, + this.observerSubject.asObservable()]); + this.marshal.trackValue(this.nativeElement, 'layout') + .pipe(takeUntil(this.destroySubject)) + .subscribe(this.onLayoutChange.bind(this)); } // ********************************************* // Lifecycle Methods // ********************************************* - ngOnChanges(changes: SimpleChanges) { - if (changes['gap'] != null || this._mqActivation) { - this._updateWithValue(); - } - } - - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ ngAfterContentInit() { - this._watchContentChanges(); - this._listenForMediaQueryChanges('gap', '0', (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + this.buildChildObservable(); + this.triggerUpdate(); } ngOnDestroy() { super.ngOnDestroy(); - if (this._layoutWatcher) { - this._layoutWatcher.unsubscribe(); - } - if (this._observer) { - this._observer.disconnect(); - } - if (this._directionWatcher) { - this._directionWatcher.unsubscribe(); + if (this.observer) { + this.observer.disconnect(); } } @@ -176,56 +150,33 @@ export class LayoutGapDirective extends BaseDirective // Protected methods // ********************************************* - /** - * Watch for child nodes to be added... and apply the layout gap styles to each. - * NOTE: this does NOT! differentiate between viewChildren and contentChildren - */ - protected _watchContentChanges() { - this._zone.runOutsideAngular(() => { - - if (typeof MutationObserver !== 'undefined') { - this._observer = new MutationObserver((mutations: MutationRecord[]) => { - const validatedChanges = (it: MutationRecord): boolean => { - return (it.addedNodes && it.addedNodes.length > 0) || - (it.removedNodes && it.removedNodes.length > 0); - }; - - // update gap styles only for child 'added' or 'removed' events - if (mutations.some(validatedChanges)) { - this._updateWithValue(); - } - }); - this._observer.observe(this.nativeElement, {childList: true}); - } - }); - } - /** * Cache the parent container 'flex-direction' and update the 'margin' styles */ - protected _onLayoutChange(layout: Layout) { - this._layout = (layout.direction || '').toLowerCase(); - if (!LAYOUT_VALUES.find(x => x === this._layout)) { - this._layout = 'row'; + protected onLayoutChange(matcher: ElementMatcher) { + const layout: string = matcher.value; + // Make sure to filter out 'wrap' option + const direction = layout.split(' '); + this.layout = direction[0]; + if (!LAYOUT_VALUES.find(x => x === this.layout)) { + this.layout = 'row'; } - this._updateWithValue(); + this.triggerUpdate(); } /** * */ - protected _updateWithValue(value?: string) { - let gapValue = value || this._queryInput('gap') || '0'; - if (this._mqActivation) { - gapValue = this._mqActivation.activatedInput; + protected updateWithValue(value: string) { + if (!value) { + value = this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY); } - // Gather all non-hidden Element nodes const items = this.childrenNodes - .filter(el => el.nodeType === 1 && this._getDisplayStyle(el) != 'none') + .filter(el => el.nodeType === 1 && this.getDisplayStyle(el) !== 'none') .sort((a, b) => { - const orderA = +this._styler.lookupStyle(a, 'order'); - const orderB = +this._styler.lookupStyle(b, 'order'); + const orderA = +this.styler.lookupStyle(a, 'order'); + const orderB = +this.styler.lookupStyle(b, 'order'); if (isNaN(orderA) || isNaN(orderB) || orderA === orderB) { return 0; } else { @@ -234,20 +185,56 @@ export class LayoutGapDirective extends BaseDirective }); if (items.length > 0) { - const directionality = this._directionality.value; - const layout = this._layout; + const directionality = this.directionality.value; + const layout = this.layout; if (layout === 'row' && directionality === 'rtl') { - this._styleCache = layoutGapCacheRowRtl; + this.styleCache = layoutGapCacheRowRtl; } else if (layout === 'row' && directionality !== 'rtl') { - this._styleCache = layoutGapCacheRowLtr; + this.styleCache = layoutGapCacheRowLtr; } else if (layout === 'column' && directionality === 'rtl') { - this._styleCache = layoutGapCacheColumnRtl; + this.styleCache = layoutGapCacheColumnRtl; } else if (layout === 'column' && directionality !== 'rtl') { - this._styleCache = layoutGapCacheColumnLtr; + this.styleCache = layoutGapCacheColumnLtr; } - this.addStyles(gapValue, {directionality, items, layout}); + this.addStyles(value, {directionality, items, layout}); } } + + /** + * Quick accessor to the current HTMLElement's `display` style + * Note: this allows us to preserve the original style + * and optional restore it when the mediaQueries deactivate + */ + protected getDisplayStyle(source: HTMLElement = this.nativeElement): string { + const query = 'display'; + return this.styler.lookupStyle(source, query); + } + + protected buildChildObservable(): void { + this.zone.runOutsideAngular(() => { + if (typeof MutationObserver !== 'undefined') { + this.observer = new MutationObserver((mutations: MutationRecord[]) => { + const validatedChanges = (it: MutationRecord): boolean => { + return (it.addedNodes && it.addedNodes.length > 0) || + (it.removedNodes && it.removedNodes.length > 0); + }; + + // update gap styles only for child 'added' or 'removed' events + if (mutations.some(validatedChanges)) { + this.observerSubject.next(); + } + }); + this.observer.observe(this.nativeElement, {childList: true}); + } + }); + } + + protected observer?: MutationObserver; +} + +@Directive({selector, inputs}) +export class DefaultLayoutGapDirective extends LayoutGapDirective { + protected inputs = inputs; } const layoutGapCacheRowRtl: Map = new Map(); diff --git a/src/lib/flex/layout/layout.ts b/src/lib/flex/layout/layout.ts index b90763413..4cf6103d0 100644 --- a/src/lib/flex/layout/layout.ts +++ b/src/lib/flex/layout/layout.ts @@ -5,51 +5,37 @@ * 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 {Directive, ElementRef, OnChanges, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, - Injectable, -} from '@angular/core'; -import { - BaseDirective, - MediaChange, - MediaMonitor, + BaseDirective2, StyleBuilder, StyleDefinition, StyleUtils, + MediaMarshaller, } from '@angular/flex-layout/core'; -import {Observable, ReplaySubject} from 'rxjs'; import {buildLayoutCSS} from '../../utils/layout-validator'; -export type Layout = { - direction: string; - wrap: boolean; -}; - -export interface LayoutParent { - announcer: ReplaySubject; -} - @Injectable({providedIn: 'root'}) export class LayoutStyleBuilder extends StyleBuilder { - buildStyles(input: string, _parent: LayoutParent) { - const styles = buildLayoutCSS(input); - return styles; - } - sideEffect(_input: string, styles: StyleDefinition, parent: LayoutParent) { - parent.announcer.next({ - direction: styles['flex-direction'] as string, - wrap: !!styles['flex-wrap'] && styles['flex-wrap'] !== 'nowrap' - }); + buildStyles(input: string) { + return buildLayoutCSS(input); } } +const inputs = [ + 'fxLayout', 'fxLayout.xs', 'fxLayout.sm', 'fxLayout.md', + 'fxLayout.lg', 'fxLayout.xl', 'fxLayout.lt-sm', 'fxLayout.lt-md', + 'fxLayout.lt-lg', 'fxLayout.lt-xl', 'fxLayout.gt-xs', 'fxLayout.gt-sm', + 'fxLayout.gt-md', 'fxLayout.gt-lg' +]; +const selector = ` + [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], + [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], + [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], + [fxLayout.gt-md], [fxLayout.gt-lg] +`; + /** * 'layout' flexbox styling directive * Defines the positioning flow direction for the child elements: row or column @@ -57,95 +43,27 @@ export class LayoutStyleBuilder extends StyleBuilder { * @see https://css-tricks.com/almanac/properties/f/flex-direction/ * */ -@Directive({selector: ` - [fxLayout], - [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], - [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], - [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg] -`}) -export class LayoutDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /** - * Create Observable for nested/child 'flex' directives. This allows - * child flex directives to subscribe/listen for flexbox direction changes. - */ - protected _announcer: ReplaySubject; - - /** - * Publish observer to enabled nested, dependent directives to listen - * to parent 'layout' direction changes - */ - layout$: Observable; - - /* tslint:disable */ - @Input('fxLayout') set layout(val: string) { this._cacheInput('layout', val); }; - @Input('fxLayout.xs') set layoutXs(val: string) { this._cacheInput('layoutXs', val); }; - @Input('fxLayout.sm') set layoutSm(val: string) { this._cacheInput('layoutSm', val); }; - @Input('fxLayout.md') set layoutMd(val: string) { this._cacheInput('layoutMd', val); }; - @Input('fxLayout.lg') set layoutLg(val: string) { this._cacheInput('layoutLg', val); }; - @Input('fxLayout.xl') set layoutXl(val: string) { this._cacheInput('layoutXl', val); }; - - @Input('fxLayout.gt-xs') set layoutGtXs(val: string) { this._cacheInput('layoutGtXs', val); }; - @Input('fxLayout.gt-sm') set layoutGtSm(val: string) { this._cacheInput('layoutGtSm', val); }; - @Input('fxLayout.gt-md') set layoutGtMd(val: string) { this._cacheInput('layoutGtMd', val); }; - @Input('fxLayout.gt-lg') set layoutGtLg(val: string) { this._cacheInput('layoutGtLg', val); }; - - @Input('fxLayout.lt-sm') set layoutLtSm(val: string) { this._cacheInput('layoutLtSm', val); }; - @Input('fxLayout.lt-md') set layoutLtMd(val: string) { this._cacheInput('layoutLtMd', val); }; - @Input('fxLayout.lt-lg') set layoutLtLg(val: string) { this._cacheInput('layoutLtLg', val); }; - @Input('fxLayout.lt-xl') set layoutLtXl(val: string) { this._cacheInput('layoutLtXl', val); }; - /* tslint:enable */ - - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils, - styleBuilder: LayoutStyleBuilder) { - super(monitor, elRef, styleUtils, styleBuilder); - this._announcer = new ReplaySubject(1); - this.layout$ = this._announcer.asObservable(); - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* - - /** - * On changes to any @Input properties... - * Default to use the non-responsive Input value ('fxLayout') - * Then conditionally override with the mq-activated Input's current value - */ - ngOnChanges(changes: SimpleChanges) { - if (changes['layout'] != null || this._mqActivation) { - this._updateWithDirection(); - } - } - - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges('layout', 'row', (changes: MediaChange) => { - this._updateWithDirection(changes.value); - }); +export class LayoutDirective extends BaseDirective2 implements OnChanges { + + protected DIRECTIVE_KEY = 'layout'; + + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: LayoutStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - // ********************************************* - // Protected methods - // ********************************************* - - /** Validate the direction value and then update the host's inline flexbox styles */ - protected _updateWithDirection(value?: string) { - value = value || this._queryInput('layout') || 'row'; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - this.addStyles(value || '', {announcer: this._announcer}); - } + protected styleCache = layoutCache; +} - protected _styleCache = layoutCache; +@Directive({selector, inputs}) +export class DefaultLayoutDirective extends LayoutDirective { + protected inputs = inputs; } const layoutCache: Map = new Map(); diff --git a/src/lib/flex/module.ts b/src/lib/flex/module.ts index 57f830248..9df86b458 100644 --- a/src/lib/flex/module.ts +++ b/src/lib/flex/module.ts @@ -9,25 +9,25 @@ import {NgModule} from '@angular/core'; import {BidiModule} from '@angular/cdk/bidi'; import {CoreModule} from '@angular/flex-layout/core'; -import {LayoutDirective} from './layout/layout'; -import {LayoutGapDirective} from './layout-gap/layout-gap'; -import {FlexDirective} from './flex/flex'; -import {FlexOrderDirective} from './flex-order/flex-order'; -import {FlexOffsetDirective} from './flex-offset/flex-offset'; -import {FlexAlignDirective} from './flex-align/flex-align'; +import {DefaultLayoutDirective} from './layout/layout'; +import {DefaultLayoutGapDirective} from './layout-gap/layout-gap'; +import {DefaultFlexDirective} from './flex/flex'; +import {DefaultFlexOrderDirective} from './flex-order/flex-order'; +import {DefaultFlexOffsetDirective} from './flex-offset/flex-offset'; +import {DefaultFlexAlignDirective} from './flex-align/flex-align'; import {FlexFillDirective} from './flex-fill/flex-fill'; -import {LayoutAlignDirective} from './layout-align/layout-align'; +import {DefaultLayoutAlignDirective} from './layout-align/layout-align'; const ALL_DIRECTIVES = [ - LayoutDirective, - LayoutGapDirective, - LayoutAlignDirective, - FlexDirective, - FlexOrderDirective, - FlexOffsetDirective, + DefaultLayoutDirective, + DefaultLayoutGapDirective, + DefaultLayoutAlignDirective, + DefaultFlexOrderDirective, + DefaultFlexOffsetDirective, FlexFillDirective, - FlexAlignDirective, + DefaultFlexAlignDirective, + DefaultFlexDirective, ]; /** diff --git a/src/lib/grid/align-columns/align-columns.ts b/src/lib/grid/align-columns/align-columns.ts index 7c739167d..f7d9d317c 100644 --- a/src/lib/grid/align-columns/align-columns.ts +++ b/src/lib/grid/align-columns/align-columns.ts @@ -5,158 +5,142 @@ * 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 {Directive, ElementRef, Injectable, Input, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; -import {extendObject} from '../../utils/object-extend'; + BaseDirective2, + StyleUtils, + StyleBuilder, + StyleDefinition, + MediaMarshaller, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'alignColumns'; const DEFAULT_MAIN = 'start'; const DEFAULT_CROSS = 'stretch'; -/** - * 'column alignment' CSS Grid styling directive - * Configures the alignment in the column direction - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-19 - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-21 - */ -@Directive({selector: ` - [gdAlignColumns], - [gdAlignColumns.xs], [gdAlignColumns.sm], [gdAlignColumns.md], - [gdAlignColumns.lg], [gdAlignColumns.xl], [gdAlignColumns.lt-sm], - [gdAlignColumns.lt-md], [gdAlignColumns.lt-lg], [gdAlignColumns.lt-xl], - [gdAlignColumns.gt-xs], [gdAlignColumns.gt-sm], [gdAlignColumns.gt-md], - [gdAlignColumns.gt-lg] -`}) -export class GridAlignColumnsDirective extends BaseDirective - implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdAlignColumns') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdAlignColumns.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdAlignColumns.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdAlignColumns.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdAlignColumns.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdAlignColumns.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdAlignColumns.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdAlignColumns.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdAlignColumns.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdAlignColumns.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdAlignColumns.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdAlignColumns.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdAlignColumns.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdAlignColumns.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); +export interface GridAlignColumnsParent { + inline: boolean; +} + +@Injectable({providedIn: 'root'}) +export class GridAlignColumnsStyleBuilder extends StyleBuilder { + buildStyles(input: string, parent: GridAlignColumnsParent) { + return buildCss(input || `${DEFAULT_MAIN} ${DEFAULT_CROSS}`, parent.inline); } +} - // ********************************************* - // Lifecycle Methods - // ********************************************* +export class GridAlignColumnsDirective extends BaseDirective2 { - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } - } + protected DIRECTIVE_KEY = 'grid-align-columns'; - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, `${DEFAULT_MAIN} ${DEFAULT_CROSS}`, - (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; + + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridAlignColumnsStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || `${DEFAULT_MAIN} ${DEFAULT_CROSS}`; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? alignColumnsInlineCache : alignColumnsCache; + this.addStyles(value, {inline: this.inline}); } +} +const alignColumnsCache: Map = new Map(); +const alignColumnsInlineCache: Map = new Map(); + +const inputs = [ + 'gdAlignColumns', + 'gdAlignColumns.xs', 'gdAlignColumns.sm', 'gdAlignColumns.md', + 'gdAlignColumns.lg', 'gdAlignColumns.xl', 'gdAlignColumns.lt-sm', + 'gdAlignColumns.lt-md', 'gdAlignColumns.lt-lg', 'gdAlignColumns.lt-xl', + 'gdAlignColumns.gt-xs', 'gdAlignColumns.gt-sm', 'gdAlignColumns.gt-md', + 'gdAlignColumns.gt-lg' +]; +const selector = ` + [gdAlignColumns], + [gdAlignColumns.xs], [gdAlignColumns.sm], [gdAlignColumns.md], + [gdAlignColumns.lg], [gdAlignColumns.xl], [gdAlignColumns.lt-sm], + [gdAlignColumns.lt-md], [gdAlignColumns.lt-lg], [gdAlignColumns.lt-xl], + [gdAlignColumns.gt-xs], [gdAlignColumns.gt-sm], [gdAlignColumns.gt-md], + [gdAlignColumns.gt-lg] +`; - protected _buildCSS(align: string = '') { - let css: {[key: string]: string} = {}, [mainAxis, crossAxis] = align.split(' '); - - // Main axis - switch (mainAxis) { - case 'center': - css['align-content'] = 'center'; - break; - case 'space-around': - css['align-content'] = 'space-around'; - break; - case 'space-between': - css['align-content'] = 'space-between'; - break; - case 'space-evenly': - css['align-content'] = 'space-evenly'; - break; - case 'end': - css['align-content'] = 'end'; - break; - case 'start': - css['align-content'] = 'start'; - break; - case 'stretch': - css['align-content'] = 'stretch'; - break; - default: - css['align-content'] = DEFAULT_MAIN; // default main axis - break; - } - - // Cross-axis - switch (crossAxis) { - case 'start': - css['align-items'] = 'start'; - break; - case 'center': - css['align-items'] = 'center'; - break; - case 'end': - css['align-items'] = 'end'; - break; - case 'stretch': - css['align-items'] = 'stretch'; - break; - default : // 'stretch' - css['align-items'] = DEFAULT_CROSS; // default cross axis - break; - } - - return extendObject(css, {'display' : this._queryInput('inline') ? 'inline-grid' : 'grid'}); +/** + * 'column alignment' CSS Grid styling directive + * Configures the alignment in the column direction + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-19 + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-21 + */ +@Directive({selector, inputs}) +export class DefaultGridAlignColumnsDirective extends GridAlignColumnsDirective { + protected inputs = inputs; +} + +function buildCss(align: string, inline: boolean): StyleDefinition { + const css: {[key: string]: string} = {}, [mainAxis, crossAxis] = align.split(' '); + + // Main axis + switch (mainAxis) { + case 'center': + css['align-content'] = 'center'; + break; + case 'space-around': + css['align-content'] = 'space-around'; + break; + case 'space-between': + css['align-content'] = 'space-between'; + break; + case 'space-evenly': + css['align-content'] = 'space-evenly'; + break; + case 'end': + css['align-content'] = 'end'; + break; + case 'start': + css['align-content'] = 'start'; + break; + case 'stretch': + css['align-content'] = 'stretch'; + break; + default: + css['align-content'] = DEFAULT_MAIN; // default main axis + break; } + + // Cross-axis + switch (crossAxis) { + case 'start': + css['align-items'] = 'start'; + break; + case 'center': + css['align-items'] = 'center'; + break; + case 'end': + css['align-items'] = 'end'; + break; + case 'stretch': + css['align-items'] = 'stretch'; + break; + default : // 'stretch' + css['align-items'] = DEFAULT_CROSS; // default cross axis + break; + } + + css['display'] = inline ? 'inline-grid' : 'grid'; + + return css; } diff --git a/src/lib/grid/align-rows/align-rows.ts b/src/lib/grid/align-rows/align-rows.ts index a144de4f2..8bf437974 100644 --- a/src/lib/grid/align-rows/align-rows.ts +++ b/src/lib/grid/align-rows/align-rows.ts @@ -5,139 +5,124 @@ * 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 {Directive, ElementRef, Injectable, Input, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; -import {extendObject} from '../../utils/object-extend'; + BaseDirective2, + StyleUtils, + StyleBuilder, + StyleDefinition, + MediaMarshaller, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'alignRows'; const DEFAULT_MAIN = 'start'; const DEFAULT_CROSS = 'stretch'; -/** - * 'row alignment' CSS Grid styling directive - * Configures the alignment in the row direction - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-18 - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-20 - */ -@Directive({selector: ` - [gdAlignRows], - [gdAlignRows.xs], [gdAlignRows.sm], [gdAlignRows.md], - [gdAlignRows.lg], [gdAlignRows.xl], [gdAlignRows.lt-sm], - [gdAlignRows.lt-md], [gdAlignRows.lt-lg], [gdAlignRows.lt-xl], - [gdAlignRows.gt-xs], [gdAlignRows.gt-sm], [gdAlignRows.gt-md], - [gdAlignRows.gt-lg] -`}) -export class GridAlignRowsDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdAlignRows') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdAlignRows.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdAlignRows.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdAlignRows.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdAlignRows.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdAlignRows.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdAlignRows.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdAlignRows.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdAlignRows.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdAlignRows.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdAlignRows.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdAlignRows.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdAlignRows.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdAlignRows.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); +export interface GridAlignRowsParent { + inline: boolean; +} + +@Injectable({providedIn: 'root'}) +export class GridAlignRowsStyleBuilder extends StyleBuilder { + buildStyles(input: string, parent: GridAlignRowsParent) { + return buildCss(input || `${DEFAULT_MAIN} ${DEFAULT_CROSS}`, parent.inline); } +} - // ********************************************* - // Lifecycle Methods - // ********************************************* +export class GridAlignRowsDirective extends BaseDirective2 { - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } - } + protected DIRECTIVE_KEY = 'grid-align-rows'; - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, `${DEFAULT_MAIN} ${DEFAULT_CROSS}`, - (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; + + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridAlignRowsStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || `${DEFAULT_MAIN} ${DEFAULT_CROSS}`; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? alignRowsInlineCache : alignRowsCache; + this.addStyles(value, {inline: this.inline}); } +} +const alignRowsCache: Map = new Map(); +const alignRowsInlineCache: Map = new Map(); + +const inputs = [ + 'gdAlignRows', + 'gdAlignRows.xs', 'gdAlignRows.sm', 'gdAlignRows.md', + 'gdAlignRows.lg', 'gdAlignRows.xl', 'gdAlignRows.lt-sm', + 'gdAlignRows.lt-md', 'gdAlignRows.lt-lg', 'gdAlignRows.lt-xl', + 'gdAlignRows.gt-xs', 'gdAlignRows.gt-sm', 'gdAlignRows.gt-md', + 'gdAlignRows.gt-lg' +]; +const selector = ` + [gdAlignRows], + [gdAlignRows.xs], [gdAlignRows.sm], [gdAlignRows.md], + [gdAlignRows.lg], [gdAlignRows.xl], [gdAlignRows.lt-sm], + [gdAlignRows.lt-md], [gdAlignRows.lt-lg], [gdAlignRows.lt-xl], + [gdAlignRows.gt-xs], [gdAlignRows.gt-sm], [gdAlignRows.gt-md], + [gdAlignRows.gt-lg] +`; - protected _buildCSS(align: string = '') { - let css: {[key: string]: string} = {}, [mainAxis, crossAxis] = align.split(' '); - - // Main axis - switch (mainAxis) { - case 'center': - case 'space-around': - case 'space-between': - case 'space-evenly': - case 'end': - case 'start': - case 'stretch': - css['justify-content'] = mainAxis; - break; - default: - css['justify-content'] = DEFAULT_MAIN; // default main axis - break; - } - - // Cross-axis - switch (crossAxis) { - case 'start': - case 'center': - case 'end': - case 'stretch': - css['justify-items'] = crossAxis; - break; - default : // 'stretch' - css['justify-items'] = DEFAULT_CROSS; // default cross axis - break; - } - - return extendObject(css, {'display' : this._queryInput('inline') ? 'inline-grid' : 'grid'}); +/** + * 'row alignment' CSS Grid styling directive + * Configures the alignment in the row direction + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-18 + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-20 + */ +@Directive({selector, inputs}) +export class DefaultGridAlignRowsDirective extends GridAlignRowsDirective { + protected inputs = inputs; +} + +function buildCss(align: string, inline: boolean): StyleDefinition { + const css: {[key: string]: string} = {}, [mainAxis, crossAxis] = align.split(' '); + + // Main axis + switch (mainAxis) { + case 'center': + case 'space-around': + case 'space-between': + case 'space-evenly': + case 'end': + case 'start': + case 'stretch': + css['justify-content'] = mainAxis; + break; + default: + css['justify-content'] = DEFAULT_MAIN; // default main axis + break; } + + // Cross-axis + switch (crossAxis) { + case 'start': + case 'center': + case 'end': + case 'stretch': + css['justify-items'] = crossAxis; + break; + default : // 'stretch' + css['justify-items'] = DEFAULT_CROSS; // default cross axis + break; + } + + css['display'] = inline ? 'inline-grid' : 'grid'; + + return css; } diff --git a/src/lib/grid/area/area.ts b/src/lib/grid/area/area.ts index e995a88ac..3d42edd0f 100644 --- a/src/lib/grid/area/area.ts +++ b/src/lib/grid/area/area.ts @@ -5,99 +5,63 @@ * 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 {Directive, ElementRef, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + BaseDirective2, + StyleUtils, + MediaMarshaller, + StyleBuilder, + StyleDefinition, +} from '@angular/flex-layout/core'; -const CACHE_KEY = 'area'; const DEFAULT_VALUE = 'auto'; -/** - * 'grid-area' CSS Grid styling directive - * Configures the name or position of an element within the grid - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-27 - */ -@Directive({selector: ` - [gdArea], - [gdArea.xs], [gdArea.sm], [gdArea.md], [gdArea.lg], [gdArea.xl], - [gdArea.lt-sm], [gdArea.lt-md], [gdArea.lt-lg], [gdArea.lt-xl], - [gdArea.gt-xs], [gdArea.gt-sm], [gdArea.gt-md], [gdArea.gt-lg] -`}) -export class GridAreaDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdArea') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdArea.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdArea.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdArea.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdArea.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdArea.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdArea.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdArea.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdArea.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdArea.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdArea.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdArea.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdArea.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdArea.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); +@Injectable({providedIn: 'root'}) +export class GridAreaStyleBuilder extends StyleBuilder { + buildStyles(input: string) { + return {'grid-area': input || DEFAULT_VALUE}; } +} - // ********************************************* - // Lifecycle Methods - // ********************************************* +export class GridAreaDirective extends BaseDirective2 { - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } - } + protected DIRECTIVE_KEY = 'grid-area'; - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridAreaStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - // ********************************************* - // Protected methods - // ********************************************* - - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } + protected styleCache = gridAreaCache; +} - this._applyStyleToElement(this._buildCSS(value)); - } +const gridAreaCache: Map = new Map(); +const inputs = [ + 'gdArea', + 'gdArea.xs', 'gdArea.sm', 'gdArea.md', 'gdArea.lg', 'gdArea.xl', + 'gdArea.lt-sm', 'gdArea.lt-md', 'gdArea.lt-lg', 'gdArea.lt-xl', + 'gdArea.gt-xs', 'gdArea.gt-sm', 'gdArea.gt-md', 'gdArea.gt-lg' +]; +const selector = ` + [gdArea], + [gdArea.xs], [gdArea.sm], [gdArea.md], [gdArea.lg], [gdArea.xl], + [gdArea.lt-sm], [gdArea.lt-md], [gdArea.lt-lg], [gdArea.lt-xl], + [gdArea.gt-xs], [gdArea.gt-sm], [gdArea.gt-md], [gdArea.gt-lg] +`; - protected _buildCSS(value: string = '') { - return {'grid-area': value}; - } +/** + * 'grid-area' CSS Grid styling directive + * Configures the name or position of an element within the grid + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-27 + */ +@Directive({selector, inputs}) +export class DefaultGridAreaDirective extends GridAreaDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/areas/areas.ts b/src/lib/grid/areas/areas.ts index 10fcefa81..515316119 100644 --- a/src/lib/grid/areas/areas.ts +++ b/src/lib/grid/areas/areas.ts @@ -5,108 +5,88 @@ * 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 {Directive, ElementRef, Injectable, Input, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + BaseDirective2, + StyleUtils, + StyleBuilder, + MediaMarshaller, + StyleDefinition, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'areas'; const DEFAULT_VALUE = 'none'; const DELIMETER = '|'; -/** - * 'grid-template-areas' CSS Grid styling directive - * Configures the names of elements within the grid - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-14 - */ -@Directive({selector: ` - [gdAreas], - [gdAreas.xs], [gdAreas.sm], [gdAreas.md], [gdAreas.lg], [gdAreas.xl], - [gdAreas.lt-sm], [gdAreas.lt-md], [gdAreas.lt-lg], [gdAreas.lt-xl], - [gdAreas.gt-xs], [gdAreas.gt-sm], [gdAreas.gt-md], [gdAreas.gt-lg] -`}) -export class GridAreasDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdAreas') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdAreas.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdAreas.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdAreas.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdAreas.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdAreas.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdAreas.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdAreas.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdAreas.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdAreas.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdAreas.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdAreas.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdAreas.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdAreas.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; +export interface GridAreasParent { + inline: boolean; +} - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; +@Injectable({providedIn: 'root'}) +export class GridAreasStyleBuiler extends StyleBuilder { + buildStyles(input: string, parent: GridAreasParent) { + const areas = (input || DEFAULT_VALUE).split(DELIMETER).map(v => `"${v.trim()}"`); - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); + return { + 'display': parent.inline ? 'inline-grid' : 'grid', + 'grid-template-areas': areas.join(' ') + }; } +} - // ********************************************* - // Lifecycle Methods - // ********************************************* +export class GridAreasDirective extends BaseDirective2 { - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } - } + protected DIRECTIVE_KEY = 'grid-areas'; - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridAreasStyleBuiler, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? areasInlineCache : areasCache; + this.addStyles(value, {inline: this.inline}); } +} +const areasCache: Map = new Map(); +const areasInlineCache: Map = new Map(); - protected _buildCSS(value: string = '') { - const areas = value.split(DELIMETER).map(v => `"${v.trim()}"`); +const inputs = [ + 'gdAreas', + 'gdAreas.xs', 'gdAreas.sm', 'gdAreas.md', 'gdAreas.lg', 'gdAreas.xl', + 'gdAreas.lt-sm', 'gdAreas.lt-md', 'gdAreas.lt-lg', 'gdAreas.lt-xl', + 'gdAreas.gt-xs', 'gdAreas.gt-sm', 'gdAreas.gt-md', 'gdAreas.gt-lg' +]; - return { - 'display': this._queryInput('inline') ? 'inline-grid' : 'grid', - 'grid-template-areas': areas.join(' ') - }; - } +const selector = ` + [gdAreas], + [gdAreas.xs], [gdAreas.sm], [gdAreas.md], [gdAreas.lg], [gdAreas.xl], + [gdAreas.lt-sm], [gdAreas.lt-md], [gdAreas.lt-lg], [gdAreas.lt-xl], + [gdAreas.gt-xs], [gdAreas.gt-sm], [gdAreas.gt-md], [gdAreas.gt-lg] +`; + +/** + * 'grid-template-areas' CSS Grid styling directive + * Configures the names of elements within the grid + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-14 + */ +@Directive({selector, inputs}) +export class DefaultGridAreasDirective extends GridAreasDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/auto/auto.ts b/src/lib/grid/auto/auto.ts index 35d4194e4..76542093d 100644 --- a/src/lib/grid/auto/auto.ts +++ b/src/lib/grid/auto/auto.ts @@ -5,112 +5,90 @@ * 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 {Directive, ElementRef, Input, Optional, Injectable} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + BaseDirective2, + StyleUtils, + StyleBuilder, + MediaMarshaller, + StyleDefinition, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'autoFlow'; const DEFAULT_VALUE = 'initial'; -/** - * 'grid-auto-flow' CSS Grid styling directive - * Configures the auto placement algorithm for the grid - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-23 - */ -@Directive({selector: ` - [gdAuto], - [gdAuto.xs], [gdAuto.sm], [gdAuto.md], [gdAuto.lg], [gdAuto.xl], - [gdAuto.lt-sm], [gdAuto.lt-md], [gdAuto.lt-lg], [gdAuto.lt-xl], - [gdAuto.gt-xs], [gdAuto.gt-sm], [gdAuto.gt-md], [gdAuto.gt-lg] -`}) -export class GridAutoDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdAuto') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdAuto.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdAuto.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdAuto.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdAuto.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdAuto.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdAuto.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdAuto.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdAuto.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdAuto.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdAuto.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdAuto.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdAuto.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdAuto.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; +export interface GridAutoParent { + inline: boolean; +} - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); - } +@Injectable({providedIn: 'root'}) +export class GridAutoStyleBuilder extends StyleBuilder { + buildStyles(input: string, parent: GridAutoParent) { + let [direction, dense] = (input || DEFAULT_VALUE).split(' '); + if (direction !== 'column' && direction !== 'row' && direction !== 'dense') { + direction = 'row'; + } - // ********************************************* - // Lifecycle Methods - // ********************************************* + dense = (dense === 'dense' && direction !== 'dense') ? ' dense' : ''; - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } + return { + 'display': parent.inline ? 'inline-grid' : 'grid', + 'grid-auto-flow': direction + dense + }; } +} - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); +export class GridAutoDirective extends BaseDirective2 { + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; + + protected DIRECTIVE_KEY = 'grid-auto'; + + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridAutoStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? autoInlineCache : autoCache; + this.addStyles(value, {inline: this.inline}); } +} +const autoCache: Map = new Map(); +const autoInlineCache: Map = new Map(); - protected _buildCSS(value: string = '') { - let [direction, dense] = value.split(' '); - if (direction !== 'column' && direction !== 'row' && direction !== 'dense') { - direction = 'row'; - } - - dense = (dense === 'dense' && direction !== 'dense') ? ' dense' : ''; +const inputs = [ + 'gdAuto', + 'gdAuto.xs', 'gdAuto.sm', 'gdAuto.md', 'gdAuto.lg', 'gdAuto.xl', + 'gdAuto.lt-sm', 'gdAuto.lt-md', 'gdAuto.lt-lg', 'gdAuto.lt-xl', + 'gdAuto.gt-xs', 'gdAuto.gt-sm', 'gdAuto.gt-md', 'gdAuto.gt-lg' +]; +const selector = ` + [gdAuto], + [gdAuto.xs], [gdAuto.sm], [gdAuto.md], [gdAuto.lg], [gdAuto.xl], + [gdAuto.lt-sm], [gdAuto.lt-md], [gdAuto.lt-lg], [gdAuto.lt-xl], + [gdAuto.gt-xs], [gdAuto.gt-sm], [gdAuto.gt-md], [gdAuto.gt-lg] +`; - return { - 'display': this._queryInput('inline') ? 'inline-grid' : 'grid', - 'grid-auto-flow': direction + dense - }; - } +/** + * 'grid-auto-flow' CSS Grid styling directive + * Configures the auto placement algorithm for the grid + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-23 + */ +@Directive({selector, inputs}) +export class DefaultGridAutoDirective extends GridAutoDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/column/column.ts b/src/lib/grid/column/column.ts index bb1db2111..49bb18416 100644 --- a/src/lib/grid/column/column.ts +++ b/src/lib/grid/column/column.ts @@ -5,99 +5,63 @@ * 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 {Directive, ElementRef, Optional, Injectable} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + BaseDirective2, + StyleUtils, + MediaMarshaller, + StyleBuilder, + StyleDefinition, +} from '@angular/flex-layout/core'; -const CACHE_KEY = 'column'; const DEFAULT_VALUE = 'auto'; -/** - * 'grid-column' CSS Grid styling directive - * Configures the name or position of an element within the grid - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-26 - */ -@Directive({selector: ` - [gdColumn], - [gdColumn.xs], [gdColumn.sm], [gdColumn.md], [gdColumn.lg], [gdColumn.xl], - [gdColumn.lt-sm], [gdColumn.lt-md], [gdColumn.lt-lg], [gdColumn.lt-xl], - [gdColumn.gt-xs], [gdColumn.gt-sm], [gdColumn.gt-md], [gdColumn.gt-lg] -`}) -export class GridColumnDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdColumn') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdColumn.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdColumn.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdColumn.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdColumn.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdColumn.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdColumn.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdColumn.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdColumn.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdColumn.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdColumn.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdColumn.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdColumn.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdColumn.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* - - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } +@Injectable({providedIn: 'root'}) +export class GridColumnStyleBuilder extends StyleBuilder { + buildStyles(input: string) { + return {'grid-column': input || DEFAULT_VALUE}; } +} - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); +export class GridColumnDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'grid-column'; - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridColumnStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - // ********************************************* - // Protected methods - // ********************************************* + protected styleCache = columnCache; +} - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } +const columnCache: Map = new Map(); - this._applyStyleToElement(this._buildCSS(value)); - } +const inputs = [ + 'gdColumn', + 'gdColumn.xs', 'gdColumn.sm', 'gdColumn.md', 'gdColumn.lg', 'gdColumn.xl', + 'gdColumn.lt-sm', 'gdColumn.lt-md', 'gdColumn.lt-lg', 'gdColumn.lt-xl', + 'gdColumn.gt-xs', 'gdColumn.gt-sm', 'gdColumn.gt-md', 'gdColumn.gt-lg' +]; +const selector = ` + [gdColumn], + [gdColumn.xs], [gdColumn.sm], [gdColumn.md], [gdColumn.lg], [gdColumn.xl], + [gdColumn.lt-sm], [gdColumn.lt-md], [gdColumn.lt-lg], [gdColumn.lt-xl], + [gdColumn.gt-xs], [gdColumn.gt-sm], [gdColumn.gt-md], [gdColumn.gt-lg] +`; - protected _buildCSS(value: string = '') { - return {'grid-column': value}; - } +/** + * 'grid-column' CSS Grid styling directive + * Configures the name or position of an element within the grid + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-26 + */ +@Directive({selector, inputs}) +export class DefaultGridColumnDirective extends GridColumnDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/columns/columns.ts b/src/lib/grid/columns/columns.ts index 17bbaacec..6a6935006 100644 --- a/src/lib/grid/columns/columns.ts +++ b/src/lib/grid/columns/columns.ts @@ -5,118 +5,98 @@ * 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 {Directive, ElementRef, Input, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + MediaMarshaller, + BaseDirective2, + StyleBuilder, + StyleDefinition, + StyleUtils, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'columns'; const DEFAULT_VALUE = 'none'; const AUTO_SPECIFIER = '!'; -/** - * 'grid-template-columns' CSS Grid styling directive - * Configures the sizing for the columns in the grid - * Syntax: [auto] - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-13 - */ -@Directive({selector: ` - [gdColumns], - [gdColumns.xs], [gdColumns.sm], [gdColumns.md], [gdColumns.lg], [gdColumns.xl], - [gdColumns.lt-sm], [gdColumns.lt-md], [gdColumns.lt-lg], [gdColumns.lt-xl], - [gdColumns.gt-xs], [gdColumns.gt-sm], [gdColumns.gt-md], [gdColumns.gt-lg] -`}) -export class GridColumnsDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdColumns') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdColumns.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdColumns.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdColumns.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdColumns.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdColumns.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdColumns.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdColumns.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdColumns.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdColumns.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdColumns.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdColumns.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdColumns.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdColumns.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; +export interface GridColumnsParent { + inline: boolean; +} - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); - } +@Injectable({providedIn: 'root'}) +export class GridColumnsStyleBuilder extends StyleBuilder { + buildStyles(input: string, parent: GridColumnsParent) { + input = input || DEFAULT_VALUE; + let auto = false; + if (input.endsWith(AUTO_SPECIFIER)) { + input = input.substring(0, input.indexOf(AUTO_SPECIFIER)); + auto = true; + } - // ********************************************* - // Lifecycle Methods - // ********************************************* + const css = { + 'display': parent.inline ? 'inline-grid' : 'grid', + 'grid-auto-columns': '', + 'grid-template-columns': '', + }; + const key = (auto ? 'grid-auto-columns' : 'grid-template-columns'); + css[key] = input; - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } + return css; } +} - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); +export class GridColumnsDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'grid-columns'; + + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; + + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridColumnsStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? columnsInlineCache : columnsCache; + this.addStyles(value, {inline: this.inline}); } +} +const columnsCache: Map = new Map(); +const columnsInlineCache: Map = new Map(); - protected _buildCSS(value: string = '') { - let auto = false; - if (value.endsWith(AUTO_SPECIFIER)) { - value = value.substring(0, value.indexOf(AUTO_SPECIFIER)); - auto = true; - } +const inputs = [ + 'gdColumns', + 'gdColumns.xs', 'gdColumns.sm', 'gdColumns.md', 'gdColumns.lg', 'gdColumns.xl', + 'gdColumns.lt-sm', 'gdColumns.lt-md', 'gdColumns.lt-lg', 'gdColumns.lt-xl', + 'gdColumns.gt-xs', 'gdColumns.gt-sm', 'gdColumns.gt-md', 'gdColumns.gt-lg' +]; - let css = { - 'display': this._queryInput('inline') ? 'inline-grid' : 'grid', - 'grid-auto-columns': '', - 'grid-template-columns': '', - }; - const key = (auto ? 'grid-auto-columns' : 'grid-template-columns'); - css[key] = value; +const selector = ` + [gdColumns], + [gdColumns.xs], [gdColumns.sm], [gdColumns.md], [gdColumns.lg], [gdColumns.xl], + [gdColumns.lt-sm], [gdColumns.lt-md], [gdColumns.lt-lg], [gdColumns.lt-xl], + [gdColumns.gt-xs], [gdColumns.gt-sm], [gdColumns.gt-md], [gdColumns.gt-lg] +`; - return css; - } +/** + * 'grid-template-columns' CSS Grid styling directive + * Configures the sizing for the columns in the grid + * Syntax: [auto] + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-13 + */ +@Directive({selector, inputs}) +export class DefaultGridColumnsDirective extends GridColumnsDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/gap/gap.ts b/src/lib/grid/gap/gap.ts index 4a5b27167..2ff7fbd52 100644 --- a/src/lib/grid/gap/gap.ts +++ b/src/lib/grid/gap/gap.ts @@ -5,106 +5,85 @@ * 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 {Directive, ElementRef, Input, Optional, Injectable} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + BaseDirective2, + StyleUtils, + MediaMarshaller, + StyleBuilder, + StyleDefinition, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'gap'; const DEFAULT_VALUE = '0'; -/** - * 'grid-gap' CSS Grid styling directive - * Configures the gap between items in the grid - * Syntax: [] - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-17 - */ -@Directive({selector: ` - [gdGap], - [gdGap.xs], [gdGap.sm], [gdGap.md], [gdGap.lg], [gdGap.xl], - [gdGap.lt-sm], [gdGap.lt-md], [gdGap.lt-lg], [gdGap.lt-xl], - [gdGap.gt-xs], [gdGap.gt-sm], [gdGap.gt-md], [gdGap.gt-lg] -`}) -export class GridGapDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdGap') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdGap.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdGap.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdGap.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdGap.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdGap.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdGap.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdGap.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdGap.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdGap.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdGap.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdGap.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdGap.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdGap.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* +export interface GridGapParent { + inline: boolean; +} - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } +@Injectable({providedIn: 'root'}) +export class GridGapStyleBuilder extends StyleBuilder { + buildStyles(input: string, parent: GridGapParent) { + return { + 'display': parent.inline ? 'inline-grid' : 'grid', + 'grid-gap': input || DEFAULT_VALUE + }; } +} - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); +export class GridGapDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'grid-gap'; + + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; + + constructor(protected elRef: ElementRef, + protected styleUtils: StyleUtils, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridGapStyleBuilder, + protected marshal: MediaMarshaller) { + super(elRef, styleBuilder, styleUtils, marshal); + this.marshal.init(this.elRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? gapInlineCache : gapCache; + this.addStyles(value, {inline: this.inline}); } +} +const gapCache: Map = new Map(); +const gapInlineCache: Map = new Map(); - protected _buildCSS(value: string = '') { - return { - 'display': this._queryInput('inline') ? 'inline-grid' : 'grid', - 'grid-gap': value - }; - } +const inputs = [ + 'gdGap', + 'gdGap.xs', 'gdGap.sm', 'gdGap.md', 'gdGap.lg', 'gdGap.xl', + 'gdGap.lt-sm', 'gdGap.lt-md', 'gdGap.lt-lg', 'gdGap.lt-xl', + 'gdGap.gt-xs', 'gdGap.gt-sm', 'gdGap.gt-md', 'gdGap.gt-lg' +]; + +const selector = ` + [gdGap], + [gdGap.xs], [gdGap.sm], [gdGap.md], [gdGap.lg], [gdGap.xl], + [gdGap.lt-sm], [gdGap.lt-md], [gdGap.lt-lg], [gdGap.lt-xl], + [gdGap.gt-xs], [gdGap.gt-sm], [gdGap.gt-md], [gdGap.gt-lg] +`; + +/** + * 'grid-gap' CSS Grid styling directive + * Configures the gap between items in the grid + * Syntax: [] + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-17 + */ +@Directive({selector, inputs}) +export class DefaultGridGapDirective extends GridGapDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/grid-align/grid-align.ts b/src/lib/grid/grid-align/grid-align.ts index c33f31439..ec608aaa0 100644 --- a/src/lib/grid/grid-align/grid-align.ts +++ b/src/lib/grid/grid-align/grid-align.ts @@ -5,142 +5,112 @@ * 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 {Directive, ElementRef, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnChanges, - OnDestroy, - OnInit, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + MediaMarshaller, + BaseDirective2, + StyleBuilder, + StyleDefinition, + StyleUtils, +} from '@angular/flex-layout/core'; -const CACHE_KEY = 'align'; const ROW_DEFAULT = 'stretch'; const COL_DEFAULT = 'stretch'; -/** - * 'align' CSS Grid styling directive for grid children - * Defines positioning of child elements along row and column axis in a grid container - * Optional values: {row-axis} values or {row-axis column-axis} value pairs - * - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#prop-justify-self - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#prop-align-self - */ -@Directive({selector: ` - [gdGridAlign], - [gdGridAlign.xs], [gdGridAlign.sm], [gdGridAlign.md], [gdGridAlign.lg],[gdGridAlign.xl], - [gdGridAlign.lt-sm], [gdGridAlign.lt-md], [gdGridAlign.lt-lg], [gdGridAlign.lt-xl], - [gdGridAlign.gt-xs], [gdGridAlign.gt-sm], [gdGridAlign.gt-md], [gdGridAlign.gt-lg] -`}) -export class GridAlignDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdGridAlign') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdGridAlign.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdGridAlign.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdGridAlign.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdGridAlign.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdGridAlign.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdGridAlign.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdGridAlign.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdGridAlign.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdGridAlign.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdGridAlign.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdGridAlign.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdGridAlign.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdGridAlign.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); +@Injectable({providedIn: 'root'}) +export class GridAlignStyleBuilder extends StyleBuilder { + buildStyles(input: string) { + return buildCss(input || ROW_DEFAULT); } +} - // ********************************************* - // Lifecycle Methods - // ********************************************* - - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } - } +export class GridAlignDirective extends BaseDirective2 { - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); + protected DIRECTIVE_KEY = 'grid-align'; - this._listenForMediaQueryChanges(CACHE_KEY, ROW_DEFAULT, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridAlignStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - // ********************************************* - // Protected methods - // ********************************************* + protected styleCache = alignCache; +} - /** - * - */ - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || ROW_DEFAULT; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } +const alignCache: Map = new Map(); - this._applyStyleToElement(this._buildCSS(value)); - } +const inputs = [ + 'gdGridAlign', + 'gdGridAlign.xs', 'gdGridAlign.sm', 'gdGridAlign.md', 'gdGridAlign.lg', 'gdGridAlign.xl', + 'gdGridAlign.lt-sm', 'gdGridAlign.lt-md', 'gdGridAlign.lt-lg', 'gdGridAlign.lt-xl', + 'gdGridAlign.gt-xs', 'gdGridAlign.gt-sm', 'gdGridAlign.gt-md', 'gdGridAlign.gt-lg' +]; - protected _buildCSS(align: string = '') { - let css: {[key: string]: string} = {}, [rowAxis, columnAxis] = align.split(' '); +const selector = ` + [gdGridAlign], + [gdGridAlign.xs], [gdGridAlign.sm], [gdGridAlign.md], [gdGridAlign.lg],[gdGridAlign.xl], + [gdGridAlign.lt-sm], [gdGridAlign.lt-md], [gdGridAlign.lt-lg], [gdGridAlign.lt-xl], + [gdGridAlign.gt-xs], [gdGridAlign.gt-sm], [gdGridAlign.gt-md], [gdGridAlign.gt-lg] +`; - // Row axis - switch (rowAxis) { - case 'end': - css['justify-self'] = 'end'; - break; - case 'center': - css['justify-self'] = 'center'; - break; - case 'stretch': - css['justify-self'] = 'stretch'; - break; - case 'start': - css['justify-self'] = 'start'; - break; - default: - css['justify-self'] = ROW_DEFAULT; // default row axis - break; - } +/** + * 'align' CSS Grid styling directive for grid children + * Defines positioning of child elements along row and column axis in a grid container + * Optional values: {row-axis} values or {row-axis column-axis} value pairs + * + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#prop-justify-self + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#prop-align-self + */ +@Directive({selector, inputs}) +export class DefaultGridAlignDirective extends GridAlignDirective { + protected inputs = inputs; +} - // Column axis - switch (columnAxis) { - case 'end': - css['align-self'] = 'end'; - break; - case 'center': - css['align-self'] = 'center'; - break; - case 'stretch': - css['align-self'] = 'stretch'; - break; - case 'start': - css['align-self'] = 'start'; - break; - default: - css['align-self'] = COL_DEFAULT; // default column axis - break; - } +function buildCss(align: string = '') { + const css: {[key: string]: string} = {}, [rowAxis, columnAxis] = align.split(' '); + + // Row axis + switch (rowAxis) { + case 'end': + css['justify-self'] = 'end'; + break; + case 'center': + css['justify-self'] = 'center'; + break; + case 'stretch': + css['justify-self'] = 'stretch'; + break; + case 'start': + css['justify-self'] = 'start'; + break; + default: + css['justify-self'] = ROW_DEFAULT; // default row axis + break; + } - return css; + // Column axis + switch (columnAxis) { + case 'end': + css['align-self'] = 'end'; + break; + case 'center': + css['align-self'] = 'center'; + break; + case 'stretch': + css['align-self'] = 'stretch'; + break; + case 'start': + css['align-self'] = 'start'; + break; + default: + css['align-self'] = COL_DEFAULT; // default column axis + break; } + + return css; } diff --git a/src/lib/grid/module.ts b/src/lib/grid/module.ts index 6bcf949e5..ae40949b4 100644 --- a/src/lib/grid/module.ts +++ b/src/lib/grid/module.ts @@ -8,31 +8,31 @@ import {NgModule} from '@angular/core'; import {CoreModule} from '@angular/flex-layout/core'; -import {GridAlignDirective} from './grid-align/grid-align'; -import {GridAlignColumnsDirective} from './align-columns/align-columns'; -import {GridAlignRowsDirective} from './align-rows/align-rows'; -import {GridAreaDirective} from './area/area'; -import {GridAreasDirective} from './areas/areas'; -import {GridAutoDirective} from './auto/auto'; -import {GridColumnDirective} from './column/column'; -import {GridColumnsDirective} from './columns/columns'; -import {GridGapDirective} from './gap/gap'; -import {GridRowDirective} from './row/row'; -import {GridRowsDirective} from './rows/rows'; +import {DefaultGridAlignDirective} from './grid-align/grid-align'; +import {DefaultGridAlignColumnsDirective} from './align-columns/align-columns'; +import {DefaultGridAlignRowsDirective} from './align-rows/align-rows'; +import {DefaultGridAreaDirective} from './area/area'; +import {DefaultGridAreasDirective} from './areas/areas'; +import {DefaultGridAutoDirective} from './auto/auto'; +import {DefaultGridColumnDirective} from './column/column'; +import {DefaultGridColumnsDirective} from './columns/columns'; +import {DefaultGridGapDirective} from './gap/gap'; +import {DefaultGridRowDirective} from './row/row'; +import {DefaultGridRowsDirective} from './rows/rows'; const ALL_DIRECTIVES = [ - GridAlignDirective, - GridAlignColumnsDirective, - GridAlignRowsDirective, - GridAreaDirective, - GridAreasDirective, - GridAutoDirective, - GridColumnDirective, - GridColumnsDirective, - GridGapDirective, - GridRowDirective, - GridRowsDirective, + DefaultGridAlignDirective, + DefaultGridAlignColumnsDirective, + DefaultGridAlignRowsDirective, + DefaultGridAreaDirective, + DefaultGridAreasDirective, + DefaultGridAutoDirective, + DefaultGridColumnDirective, + DefaultGridColumnsDirective, + DefaultGridGapDirective, + DefaultGridRowDirective, + DefaultGridRowsDirective, ]; /** diff --git a/src/lib/grid/row/row.ts b/src/lib/grid/row/row.ts index 664009ccc..3a6e651ce 100644 --- a/src/lib/grid/row/row.ts +++ b/src/lib/grid/row/row.ts @@ -5,99 +5,63 @@ * 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 {Directive, ElementRef, Optional, Injectable} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + BaseDirective2, + StyleUtils, + MediaMarshaller, + StyleBuilder, + StyleDefinition, +} from '@angular/flex-layout/core'; -const CACHE_KEY = 'row'; const DEFAULT_VALUE = 'auto'; -/** - * 'grid-row' CSS Grid styling directive - * Configures the name or position of an element within the grid - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-26 - */ -@Directive({selector: ` - [gdRow], - [gdRow.xs], [gdRow.sm], [gdRow.md], [gdRow.lg], [gdRow.xl], - [gdRow.lt-sm], [gdRow.lt-md], [gdRow.lt-lg], [gdRow.lt-xl], - [gdRow.gt-xs], [gdRow.gt-sm], [gdRow.gt-md], [gdRow.gt-lg] -`}) -export class GridRowDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdRow') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdRow.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdRow.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdRow.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdRow.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdRow.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdRow.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdRow.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdRow.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdRow.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdRow.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdRow.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdRow.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdRow.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); - } - - // ********************************************* - // Lifecycle Methods - // ********************************************* - - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } +@Injectable({providedIn: 'root'}) +export class GridRowStyleBuilder extends StyleBuilder { + buildStyles(input: string) { + return {'grid-row': input || DEFAULT_VALUE}; } +} - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); +export class GridRowDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'grid-row'; - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridRowStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.addStyles.bind(this)); } - // ********************************************* - // Protected methods - // ********************************************* + protected styleCache = rowCache; +} - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } +const rowCache: Map = new Map(); - this._applyStyleToElement(this._buildCSS(value)); - } +const inputs = [ + 'gdRow', + 'gdRow.xs', 'gdRow.sm', 'gdRow.md', 'gdRow.lg', 'gdRow.xl', + 'gdRow.lt-sm', 'gdRow.lt-md', 'gdRow.lt-lg', 'gdRow.lt-xl', + 'gdRow.gt-xs', 'gdRow.gt-sm', 'gdRow.gt-md', 'gdRow.gt-lg' +]; +const selector = ` + [gdRow], + [gdRow.xs], [gdRow.sm], [gdRow.md], [gdRow.lg], [gdRow.xl], + [gdRow.lt-sm], [gdRow.lt-md], [gdRow.lt-lg], [gdRow.lt-xl], + [gdRow.gt-xs], [gdRow.gt-sm], [gdRow.gt-md], [gdRow.gt-lg] +`; - protected _buildCSS(value: string = '') { - return {'grid-row': value}; - } +/** + * 'grid-row' CSS Grid styling directive + * Configures the name or position of an element within the grid + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-26 + */ +@Directive({selector, inputs}) +export class DefaultGridRowDirective extends GridRowDirective { + protected inputs = inputs; } diff --git a/src/lib/grid/rows/rows.ts b/src/lib/grid/rows/rows.ts index ce69fc560..89ec55a1b 100644 --- a/src/lib/grid/rows/rows.ts +++ b/src/lib/grid/rows/rows.ts @@ -5,118 +5,98 @@ * 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 {Directive, ElementRef, Input, Injectable, Optional} from '@angular/core'; import { - Directive, - ElementRef, - Input, - OnInit, - OnChanges, - OnDestroy, - SimpleChanges, -} from '@angular/core'; -import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; + MediaMarshaller, + BaseDirective2, + StyleBuilder, + StyleDefinition, + StyleUtils, +} from '@angular/flex-layout/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -const CACHE_KEY = 'rows'; const DEFAULT_VALUE = 'none'; const AUTO_SPECIFIER = '!'; -/** - * 'grid-template-rows' CSS Grid styling directive - * Configures the sizing for the rows in the grid - * Syntax: [auto] - * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-13 - */ -@Directive({selector: ` - [gdRows], - [gdRows.xs], [gdRows.sm], [gdRows.md], [gdRows.lg], [gdRows.xl], - [gdRows.lt-sm], [gdRows.lt-md], [gdRows.lt-lg], [gdRows.lt-xl], - [gdRows.gt-xs], [gdRows.gt-sm], [gdRows.gt-md], [gdRows.gt-lg] -`}) -export class GridRowsDirective extends BaseDirective implements OnInit, OnChanges, OnDestroy { - - /* tslint:disable */ - @Input('gdRows') set align(val: string) { this._cacheInput(`${CACHE_KEY}`, val); } - @Input('gdRows.xs') set alignXs(val: string) { this._cacheInput(`${CACHE_KEY}Xs`, val); } - @Input('gdRows.sm') set alignSm(val: string) { this._cacheInput(`${CACHE_KEY}Sm`, val); }; - @Input('gdRows.md') set alignMd(val: string) { this._cacheInput(`${CACHE_KEY}Md`, val); }; - @Input('gdRows.lg') set alignLg(val: string) { this._cacheInput(`${CACHE_KEY}Lg`, val); }; - @Input('gdRows.xl') set alignXl(val: string) { this._cacheInput(`${CACHE_KEY}Xl`, val); }; - - @Input('gdRows.gt-xs') set alignGtXs(val: string) { this._cacheInput(`${CACHE_KEY}GtXs`, val); }; - @Input('gdRows.gt-sm') set alignGtSm(val: string) { this._cacheInput(`${CACHE_KEY}GtSm`, val); }; - @Input('gdRows.gt-md') set alignGtMd(val: string) { this._cacheInput(`${CACHE_KEY}GtMd`, val); }; - @Input('gdRows.gt-lg') set alignGtLg(val: string) { this._cacheInput(`${CACHE_KEY}GtLg`, val); }; - - @Input('gdRows.lt-sm') set alignLtSm(val: string) { this._cacheInput(`${CACHE_KEY}LtSm`, val); }; - @Input('gdRows.lt-md') set alignLtMd(val: string) { this._cacheInput(`${CACHE_KEY}LtMd`, val); }; - @Input('gdRows.lt-lg') set alignLtLg(val: string) { this._cacheInput(`${CACHE_KEY}LtLg`, val); }; - @Input('gdRows.lt-xl') set alignLtXl(val: string) { this._cacheInput(`${CACHE_KEY}LtXl`, val); }; - - @Input('gdInline') set inline(val: string) { this._cacheInput('inline', coerceBooleanProperty(val)); }; +export interface GridRowsParent { + inline: boolean; +} - /* tslint:enable */ - constructor(monitor: MediaMonitor, - elRef: ElementRef, - styleUtils: StyleUtils) { - super(monitor, elRef, styleUtils); - } +@Injectable({providedIn: 'root'}) +export class GridRowsStyleBuilder extends StyleBuilder { + buildStyles(input: string, parent: GridRowsParent) { + input = input || DEFAULT_VALUE; + let auto = false; + if (input.endsWith(AUTO_SPECIFIER)) { + input = input.substring(0, input.indexOf(AUTO_SPECIFIER)); + auto = true; + } - // ********************************************* - // Lifecycle Methods - // ********************************************* + const css = { + 'display': parent.inline ? 'inline-grid' : 'grid', + 'grid-auto-rows': '', + 'grid-template-rows': '', + }; + const key = (auto ? 'grid-auto-rows' : 'grid-template-rows'); + css[key] = input; - /** - * For @Input changes on the current mq activation property, see onMediaQueryChanges() - */ - ngOnChanges(changes: SimpleChanges) { - if (changes[CACHE_KEY] != null || this._mqActivation) { - this._updateWithValue(); - } + return css; } +} - /** - * After the initial onChanges, build an mqActivation object that bridges - * mql change events to onMediaQueryChange handlers - */ - ngOnInit() { - super.ngOnInit(); - - this._listenForMediaQueryChanges(CACHE_KEY, DEFAULT_VALUE, (changes: MediaChange) => { - this._updateWithValue(changes.value); - }); - this._updateWithValue(); +export class GridRowsDirective extends BaseDirective2 { + protected DIRECTIVE_KEY = 'grid-rows'; + + @Input('gdInline') + get inline(): boolean { return this._inline; } + set inline(val: boolean) { this._inline = coerceBooleanProperty(val); } + protected _inline = false; + + constructor(protected elementRef: ElementRef, + // NOTE: not actually optional, but we need to force DI without a + // constructor call + @Optional() protected styleBuilder: GridRowsStyleBuilder, + protected styler: StyleUtils, + protected marshal: MediaMarshaller) { + super(elementRef, styleBuilder, styler, marshal); + this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, + this.updateWithValue.bind(this)); } // ********************************************* // Protected methods // ********************************************* - protected _updateWithValue(value?: string) { - value = value || this._queryInput(CACHE_KEY) || DEFAULT_VALUE; - if (this._mqActivation) { - value = this._mqActivation.activatedInput; - } - - this._applyStyleToElement(this._buildCSS(value)); + protected updateWithValue(value: string) { + this.styleCache = this.inline ? rowsInlineCache : rowsCache; + this.addStyles(value, {inline: this.inline}); } +} +const rowsCache: Map = new Map(); +const rowsInlineCache: Map = new Map(); - protected _buildCSS(value: string = '') { - let auto = false; - if (value.endsWith(AUTO_SPECIFIER)) { - value = value.substring(0, value.indexOf(AUTO_SPECIFIER)); - auto = true; - } +const inputs = [ + 'gdRows', + 'gdRows.xs', 'gdRows.sm', 'gdRows.md', 'gdRows.lg', 'gdRows.xl', + 'gdRows.lt-sm', 'gdRows.lt-md', 'gdRows.lt-lg', 'gdRows.lt-xl', + 'gdRows.gt-xs', 'gdRows.gt-sm', 'gdRows.gt-md', 'gdRows.gt-lg' +]; - let css = { - 'display': this._queryInput('inline') ? 'inline-grid' : 'grid', - 'grid-auto-rows': '', - 'grid-template-rows': '', - }; - const key = (auto ? 'grid-auto-rows' : 'grid-template-rows'); - css[key] = value; +const selector = ` + [gdRows], + [gdRows.xs], [gdRows.sm], [gdRows.md], [gdRows.lg], [gdRows.xl], + [gdRows.lt-sm], [gdRows.lt-md], [gdRows.lt-lg], [gdRows.lt-xl], + [gdRows.gt-xs], [gdRows.gt-sm], [gdRows.gt-md], [gdRows.gt-lg] +`; - return css; - } +/** + * 'grid-template-rows' CSS Grid styling directive + * Configures the sizing for the rows in the grid + * Syntax: [auto] + * @see https://css-tricks.com/snippets/css/complete-guide-grid/#article-header-id-13 + */ +@Directive({selector, inputs}) +export class DefaultGridRowsDirective extends GridRowsDirective { + protected inputs = inputs; } diff --git a/src/lib/server/server-provider.ts b/src/lib/server/server-provider.ts index cf1d6e30b..9c709a11a 100644 --- a/src/lib/server/server-provider.ts +++ b/src/lib/server/server-provider.ts @@ -15,7 +15,8 @@ import { BreakPoint, MatchMedia, StylesheetMap, - ServerMatchMedia + ServerMatchMedia, + prioritySort, } from '@angular/flex-layout/core'; @@ -24,7 +25,7 @@ import { * retrieve the associated stylings from the virtual stylesheet * @param serverSheet the virtual stylesheet that stores styles for each * element - * @param matchMedia the service to activate/deactive breakpoints + * @param matchMedia the service to activate/deactivate breakpoints * @param breakpoints the registered breakpoints to activate/deactivate */ export function generateStaticFlexLayoutStyles(serverSheet: StylesheetMap, @@ -41,6 +42,7 @@ export function generateStaticFlexLayoutStyles(serverSheet: StylesheetMap, const defaultStyles = new Map(serverSheet.stylesheet); let styleText = generateCss(defaultStyles, 'all', classMap); + breakpoints.sort(prioritySort); breakpoints.reverse(); breakpoints.forEach((bp, i) => { serverSheet.clearStyles(); diff --git a/yarn.lock b/yarn.lock index 5083b64df..2cefe6a2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,32 +3,32 @@ "@angular/animations@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.1.1.tgz#8fecbd19417364946a9ea40c8fdf32462110232f" - integrity sha512-iTNxhPPraCZsE4rgM23lguT1kDV4mfYAr+Bsi5J0+v9ZJA+VaKvi6eRW8ZGrx4/rDz6hzTnBn1jgPppHFbsOcw== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.1.2.tgz#876598802a2722b97d7aa9ea092d3aadc05c1fa8" + integrity sha512-zCLzPpifD4V9C35+DG75yHiAxZrWmk7n7dudxchKXf/YpgzV1M43lTSxna6YZgMLIXRjilfjfh6jqOOP+PctoQ== dependencies: tslib "^1.9.0" "@angular/cdk@^7.0.3": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-7.1.0.tgz#7e5c3e3631947ef91413a83997ec4edec92cdd1c" - integrity sha512-dY740pKcIRtKr6n6NomrgqfdEj988urTZ9I/bfJjxF5fdhnSjyhEvDlB55EHsrF+bTTZbZXRmv7AwOQ9GJnD9w== + version "7.1.1" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-7.1.1.tgz#6916b2303af5e0a56a9b64ff4d54642cf191c54f" + integrity sha512-woW9lWDBKRuxZipMzWofrAY7YpuZd4vf/J1YPjmAqV7U94MaDFyizRLyFolbTZVYo8ggh9U3SQAWRrEBvJNsjg== dependencies: tslib "^1.7.1" optionalDependencies: parse5 "^5.0.0" "@angular/common@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.1.1.tgz#f78f884614ef81ab2fd648f1aa3e83aae370a6c8" - integrity sha512-SngekFx9v39sjgi9pON0Wehxpu+NdUk7OEebw4Fa8dKqTgydTkuhmnNH+9WQe264asoeCt51oufPRjIqMLNohA== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.1.2.tgz#506e48b18dd8c9dd8c97e61585ad0647079e6c05" + integrity sha512-Ss9OilnbKpfkkwa1spUUAzgtGgd76j+Cgp1ecBBaueBoHyDZcSwD3Ioe5/91mjGF8i/MmpoBtEmk569fwmb7iQ== dependencies: tslib "^1.9.0" "@angular/compiler-cli@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-7.1.1.tgz#c5f6225fb72b56f42fa78c332fdee9755c64604e" - integrity sha512-4NXlkDhOEQgaP3Agigqw93CvXJvsfnXa0xiglq9e/wjL+6XbtM9WcDb5lfRQz41N9RSkO3pEHGvKMweKZGgogA== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-7.1.2.tgz#8bdf883b6e529ccd8111f1adc92c3323fa11d093" + integrity sha512-u686o7eOPxSokE3l+lpSMs+sGRTLiGBXGsTuNR891XPN8+E5ep7NHgimeLizVXlbwIYZiNtcQ9zRbhEsMI2ErQ== dependencies: canonical-path "1.0.0" chokidar "^1.4.2" @@ -43,67 +43,67 @@ yargs "9.0.1" "@angular/compiler@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.1.1.tgz#4efbcad27ab43d4cd36d936a8df2e073f6d02d0a" - integrity sha512-oJvBe8XZ+DXF/W/DxWBTbBcixJTuPeZWdkcZIGWhJoQP7K5GnGnj8ffP9Lp6Dh4TKv85awtC6OfIKhbHxa650Q== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.1.2.tgz#def5a55616ef805963288b9d9402d87411d93e7a" + integrity sha512-ua6Wh+c5XzxAeJT6guwAFYnwa1XzJpncppUrceRXIS9VAn9X7ApxRr45DvbVeYwXBb1iNdHWtZFm1koFVQpydA== dependencies: tslib "^1.9.0" "@angular/core@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.1.1.tgz#9748b0103cd86226554e1ccbd0f43dd8c46f1ed1" - integrity sha512-Osig5SRgDRQ+Hec/liN7nq/BCJieB+4/pqRh9rFbOXezb2ptgRZqdXOXN8P17i4AwPVf308Mh55V0niJ5Eu3Rw== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.1.2.tgz#08f90d8e6ecb26c10cc21e2e2e4bdca5d0b7d7a5" + integrity sha512-k3hKz6oj5KAaU/R034flxa73MWoR1SBBZPbpqK5zncIYbZMxvUQDgD3O7SNdQfI9G534SzdJk3AqJNEDTFUyYA== dependencies: tslib "^1.9.0" "@angular/forms@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.1.1.tgz#d16ef10a901c007062fd19144cd77917ef55ee24" - integrity sha512-yCWuPjpu23Wc3XUw7v/ACNn/e249oT0bYlM8aaMQ1F5OwrmmC4NJC12Rpl9Ihza61RIHIKzNcHVEgzc7WhcSag== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.1.2.tgz#17a427e27f496e41677be05c5d8033899db9e942" + integrity sha512-L7LtvjcZUf4DjeDKQnxm+AzC9VkmR3I+hnezyvkLT7oUHcHEpYgNtiLmNM4Ir7ZI3zuaSMmHlEBlnDn0YJlcvA== dependencies: tslib "^1.9.0" "@angular/http@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-7.1.1.tgz#f19f17ad42e7f3cdabcf1250ca757640d0f02219" - integrity sha512-pRk+c/kz9aJ8te5xzCxlPLpFnwB0d/E9YkOo3/ydaXF9vZw13RTzk00YyzJ41PDzJf8oPDdXtueTQ+vtJ7Srtw== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-7.1.2.tgz#928fd412c39a79fec3e67b01601d428c9cc8d43d" + integrity sha512-8wscCWG+Cd+/IKniYrBViMFWFZFNh8eEkmUAucPInwmcSFyY//ZLWd2WJLEqbclAGT7kOkTOdUjJ6eMnnWAFuw== dependencies: tslib "^1.9.0" "@angular/material@^7.0.3": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@angular/material/-/material-7.1.0.tgz#441f2d03a97a1fa35546b61dcde86db34f0f5efc" - integrity sha512-bgotNpSfGLjNZ1AcTyhs6XS7trF4I7UHwQmfa0l8y3Gf9plwErPDfQe2XqnayRyG9nTwHj9f1lQ45X5mr3/0/A== + version "7.1.1" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-7.1.1.tgz#147b7565262e04d504c8dab7317632967a3db02a" + integrity sha512-wjYAWsdWpb8/BgoIfoUomnycoljU00avJ3hRIgPNnEpZhB7zqiBA8tCitzDS4NK8dKBJjM9WRAOj6yl6x3+9wA== dependencies: tslib "^1.7.1" "@angular/platform-browser-dynamic@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.1.1.tgz#6945298446173338782f437a996226110cda0d3e" - integrity sha512-ZIu48Vn4S6gjD7CMbGlKGaPQ8v9rYkWzlNYi4vTYzgiqKKNC3hqLsVESU3mSvr5oeQBxSIBidTdHSyafHFrA2w== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.1.2.tgz#1eb7a08f011e911817c9cdfaa560523cedba959a" + integrity sha512-DoQ+840d3YSC34NnCVD+NlQOyes56+Re9V62ZViXKSwsWtpqgsYBiUW5yYHCO8bruS+Kn+BGTCK/w7/KEM60tg== dependencies: tslib "^1.9.0" "@angular/platform-browser@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.1.1.tgz#a6bd408f656dc43ee5a2d8af3dfaa786c7c1dfca" - integrity sha512-I6OPjecynGJSbPtzu0gvEgSmIR6X6/xEAhg4L9PycW1ryjzptTC9klWRTWIqsIBqMxhVnY44uKLeRNrDwMOwyA== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.1.2.tgz#7f1fa3b59473b1ddaaa658bf064cf512f27c6578" + integrity sha512-cxFCqOXfLznHNI3dfnKcSCokbuSrxSLlXdE4uqoZliTRQIC9/ccrxVdx4UbJjtSgWFaNG1ocxH0rckgcUEG/kg== dependencies: tslib "^1.9.0" "@angular/platform-server@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-7.1.1.tgz#adecd03e586d2f62fee7417e68897067e59aa686" - integrity sha512-wG9Xk8R6rWkWIfDQ31IwrL7gAu0gRx/vJW2We1kz1hHKOqnCB22qOTEaFY0hPfKFlwvwcZknm4KFXAizA8ub4A== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-7.1.2.tgz#2ffac676e752125a9d70d01af309e6105f7cd903" + integrity sha512-w7cIzWP9tc2xAdo7r81W3UNDyYO8y37lz8G+o9QyFRj4/bdwsVyqmpwKM/NHlsCt/Gce0gNjgPAlhU9gug780Q== dependencies: domino "^2.1.0" tslib "^1.9.0" xhr2 "^0.1.4" "@angular/router@^7.0.3": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-7.1.1.tgz#80a4cdffc03a529b73485c2ad63a30ec435364ea" - integrity sha512-jbnqEq/1iDBkeH8Vn13hauGPTzhwllWM+MLfmdNGTiMzGRx4pmkWa57seDOeBF/GNYBL9JjkWTCrkKFAc2FJKw== + version "7.1.2" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-7.1.2.tgz#3a322f6ee912d309e5f65eb10c7db6cf376d82c8" + integrity sha512-Lht4hcbx2hAtUEcJ1YG4Q63bukKrDHxqSnELMYi1/G5y5vH8LWPQX7aoEcOJeaQWQTKroQBAIeprol/h9vkvoQ== dependencies: tslib "^1.9.0" @@ -115,17 +115,17 @@ "@babel/highlight" "^7.0.0" "@babel/core@^7.1.2": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.6.tgz#3733cbee4317429bc87c62b29cf8587dba7baeb3" - integrity sha512-Hz6PJT6e44iUNpAn8AoyAs6B3bl60g7MJQaI0rZEar6ECzh6+srYO1xlIdssio34mPaUtAb1y+XlkkSJzok3yw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.0.tgz#a4dd3814901998e93340f0086e9867fefa163ada" + integrity sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.1.6" - "@babel/helpers" "^7.1.5" - "@babel/parser" "^7.1.6" + "@babel/generator" "^7.2.0" + "@babel/helpers" "^7.2.0" + "@babel/parser" "^7.2.0" "@babel/template" "^7.1.2" "@babel/traverse" "^7.1.6" - "@babel/types" "^7.1.6" + "@babel/types" "^7.2.0" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -134,12 +134,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.1.6": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.6.tgz#001303cf87a5b9d093494a4bf251d7b5d03d3999" - integrity sha512-brwPBtVvdYdGxtenbQgfCdDPmtkmUBZPjUoK5SXJEBuHaA5BCubh9ly65fzXz7R6o5rA76Rs22ES8Z+HCc0YIQ== +"@babel/generator@^7.1.6", "@babel/generator@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.0.tgz#eaf3821fa0301d9d4aef88e63d4bcc19b73ba16c" + integrity sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg== dependencies: - "@babel/types" "^7.1.6" + "@babel/types" "^7.2.0" jsesc "^2.5.1" lodash "^4.17.10" source-map "^0.5.0" @@ -168,14 +168,14 @@ dependencies: "@babel/types" "^7.0.0" -"@babel/helpers@^7.1.5": - version "7.1.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.1.5.tgz#68bfc1895d685f2b8f1995e788dbfe1f6ccb1996" - integrity sha512-2jkcdL02ywNBry1YNFAH/fViq4fXG0vdckHqeJk+75fpQ2OH+Az6076tX/M0835zA45E0Cqa6pV5Kiv9YOqjEg== +"@babel/helpers@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.2.0.tgz#8335f3140f3144270dc63c4732a4f8b0a50b7a21" + integrity sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A== dependencies: "@babel/template" "^7.1.2" "@babel/traverse" "^7.1.5" - "@babel/types" "^7.1.5" + "@babel/types" "^7.2.0" "@babel/highlight@^7.0.0": version "7.0.0" @@ -186,10 +186,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.2", "@babel/parser@^7.1.6": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.6.tgz#16e97aca1ec1062324a01c5a6a7d0df8dd189854" - integrity sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.2", "@babel/parser@^7.1.6", "@babel/parser@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.0.tgz#02d01dbc330b6cbf36b76ac93c50752c69027065" + integrity sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg== "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" @@ -215,10 +215,10 @@ globals "^11.1.0" lodash "^4.17.10" -"@babel/types@^7.0.0", "@babel/types@^7.1.2", "@babel/types@^7.1.5", "@babel/types@^7.1.6": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.6.tgz#0adb330c3a281348a190263aceb540e10f04bcce" - integrity sha512-DMiUzlY9DSjVsOylJssxLHSgj6tWM9PRFJOGW/RaOglVOK9nzTxoOMfTfRQXGUCUQ/HmlG2efwC+XqUEJ5ay4w== +"@babel/types@^7.0.0", "@babel/types@^7.1.2", "@babel/types@^7.1.6", "@babel/types@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.0.tgz#7941c5b2d8060e06f9601d6be7c223eef906d5d8" + integrity sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A== dependencies: esutils "^2.0.2" lodash "^4.17.10" @@ -240,17 +240,17 @@ tslib "1.9.0" xmlhttprequest "1.8.0" -"@firebase/auth-types@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.3.4.tgz#253b1b2d9b520a0b945d4617c8418f0f19a4159f" - integrity sha512-0r3gSQk9jw5orFHCTUIgao0zan6dHt2J0BO3t/uEzbod+uwqvUn/gh+yg+kK6HX92Fg8E7y030KX4Bw/aXt0Ew== +"@firebase/auth-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.5.0.tgz#5620308993d524e5b4f0d084372ad7e7dc7dee55" + integrity sha512-DSjtsIjTy5RSiWyGHqiGkLcDgEgFEf2aD2O0t/0+lHmAzxUGrJFO5+IkPNV6i0ffmtiJaXQDJ7z7q4OdypDBCg== -"@firebase/auth@0.7.9": - version "0.7.9" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.7.9.tgz#f915f8eeb6ee16b827518f48a0fa529803d39e4e" - integrity sha512-m8e2KZ/WvToTMovoBI5K3N0Ku8Aqt6083oqzQODUYHjf94KsAfGdoQEaJono7T7vK4o7E5xpqFgFldOM5LdgiQ== +"@firebase/auth@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.9.0.tgz#c815f36c9b0e494c50a15b93ce0d958f4eed0f29" + integrity sha512-pNxfxFr4/tJluGfmPYUiuy2Tq/ZSRniJlWP4fj1mg+a9r+KevheeISZdWEZwJMosieXeCg+BzY0tvFD9j5ZrDw== dependencies: - "@firebase/auth-types" "0.3.4" + "@firebase/auth-types" "0.5.0" "@firebase/database-types@0.3.2": version "0.3.2" @@ -268,20 +268,20 @@ faye-websocket "0.11.1" tslib "1.9.0" -"@firebase/firestore-types@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-0.7.0.tgz#bded7892868cf6b189a121d0f0cec468f1609995" - integrity sha512-jyKRcKnSh3CSEPL4xGOZNoOXEiv7YmFK/JEcdd/4cAH17/Xo+Pk67gk1E648LRKh6QPghgNvzNTY5R10mKbQNw== +"@firebase/firestore-types@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-0.8.0.tgz#a40b2ed03eda9c189af145acdff408e562976345" + integrity sha512-FdLy2TbZ6aAeT9eDmVMPAFsUqhjN2e+jcdVpl0Pz1W6ElRWWyr30hgTY7xIqIKpMs1iT6IF/8w9CKvI6fPbKxA== -"@firebase/firestore@0.8.8": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-0.8.8.tgz#17d7fccf7afb4b27d4c55e608f03fff55bad163a" - integrity sha512-8OxRQZnvkXucZNxJh0OYkRoCt0+v260ScQvSIfTPjNO26Ahxc0ULSiox6US6lHH8U1ecTEilVYroknzwV/moJw== +"@firebase/firestore@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-0.9.0.tgz#37a6698b1805b07181455df562f7e98192d12546" + integrity sha512-ub9BXce75D7t2yQxqopJhqE5YrsdePLg7wP8aMLR2Twe+O89IZqOhdbE0VALjplD2rAWXkOLqf6zYsppu2hh4g== dependencies: - "@firebase/firestore-types" "0.7.0" + "@firebase/firestore-types" "0.8.0" "@firebase/logger" "0.1.2" "@firebase/webchannel-wrapper" "0.2.11" - grpc "1.16.0" + grpc "1.16.1" tslib "1.9.0" "@firebase/functions-types@0.2.1": @@ -667,9 +667,9 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*", "@types/node@^10.1.0": - version "10.12.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.10.tgz#4fa76e6598b7de3f0cb6ec3abacc4f59e5b3a2ce" - integrity sha512-8xZEYckCbUVgK8Eg7lf5Iy4COKJ5uXlnIOnePN0WUwSQggy9tolM+tDJf7wMOnT/JT/W9xDYIaYggt3mRV2O5w== + version "10.12.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" + integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== "@types/node@^6.0.46": version "6.14.2" @@ -1248,15 +1248,15 @@ atob@^2.1.1: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^9.0.0: - version "9.3.1" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.3.1.tgz#71b622174de2b783d5fd99f9ad617b7a3c78443e" - integrity sha512-DY9gOh8z3tnCbJ13JIWaeQsoYncTGdsrgCceBaQSIL4nvdrLxgbRSBPevg2XbX7u4QCSfLheSJEEIUUSlkbx6Q== + version "9.4.2" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.4.2.tgz#0234d20900684fc4bfb67926493deb68384067f5" + integrity sha512-tYQYJvZvqlJCzF+BLC//uAcdT/Yy4ik9bwZRXr/EehUJ/bjjpTthsWTy8dpowdoIE1sLCDf1ch4Eb2cOSzZC9w== dependencies: - browserslist "^4.3.3" - caniuse-lite "^1.0.30000898" + browserslist "^4.3.5" + caniuse-lite "^1.0.30000914" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.5" + postcss "^7.0.6" postcss-value-parser "^3.3.1" aws-sign2@~0.7.0: @@ -1379,9 +1379,9 @@ better-assert@~1.0.0: callsite "1.0.0" big-integer@^1.6.17: - version "1.6.36" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" - integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== + version "1.6.40" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.40.tgz#02e4cd4d6e266c4d9ece2469c05cb6439149fc78" + integrity sha512-CjhtJp0BViLzP1ZkEnoywjgtFQXS2pomKjAJtIISTCnuHILkLcAXLdFLG/nxsHc4s9kJfc+82Xpg8WNyhfACzQ== binary-extensions@^1.0.0: version "1.12.0" @@ -1512,14 +1512,14 @@ braces@^2.3.0, braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -browserslist@^4.3.3: - version "4.3.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.4.tgz#4477b737db6a1b07077275b24791e680d4300425" - integrity sha512-u5iz+ijIMUlmV8blX82VGFrB9ecnUg5qEt55CMZ/YJEhha+d8qpBfOFuutJ6F/VKRXjZoD33b6uvarpPxcl3RA== +browserslist@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.5.tgz#1a917678acc07b55606748ea1adf9846ea8920f7" + integrity sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w== dependencies: - caniuse-lite "^1.0.30000899" - electron-to-chromium "^1.3.82" - node-releases "^1.0.1" + caniuse-lite "^1.0.30000912" + electron-to-chromium "^1.3.86" + node-releases "^1.0.5" browserstack@1.5.0: version "1.5.0" @@ -1742,10 +1742,10 @@ camelcase@^4.0.0, camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -caniuse-lite@^1.0.30000898, caniuse-lite@^1.0.30000899: - version "1.0.30000912" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000912.tgz#08e650d4090a9c0ab06bfd2b46b7d3ad6dcaea28" - integrity sha512-M3zAtV36U+xw5mMROlTXpAHClmPAor6GPKAMD5Yi7glCB5sbMPFtnQ3rGpk4XqPdUrrTIaVYSJZxREZWNy8QJg== +caniuse-lite@^1.0.30000912, caniuse-lite@^1.0.30000914: + version "1.0.30000916" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000916.tgz#3428d3f529f0a7b2bfaaec65e796037bdd433aab" + integrity sha512-D6J9jloPm2MPkg0PXcODLMQAJKkeixKO9xhqTUMvtd44MtTYMyyDXPQ2Lk9IgBq5FH0frwiPa/N/w8ncQf7kIQ== canonical-path@0.0.2, canonical-path@~0.0.2: version "0.0.2" @@ -2517,9 +2517,9 @@ core-js@2.5.5: integrity sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs= core-js@^2.0.0, core-js@^2.2.0, core-js@^2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" - integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== + version "2.6.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4" + integrity sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw== core-js@~2.3.0: version "2.3.0" @@ -3046,15 +3046,10 @@ dom-storage@2.1.0: resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.1.0.tgz#00fb868bc9201357ea243c7bcfd3304c1e34ea39" integrity sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q== -domelementtype@1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.2.1.tgz#578558ef23befac043a1abb0db07635509393479" - integrity sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA== - -domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" - integrity sha1-sXrtguirWeUt2cGbF1bg/BhyBMI= +domelementtype@1, domelementtype@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== domelementtype@~1.1.1: version "1.1.3" @@ -3069,9 +3064,9 @@ domhandler@^2.3.0: domelementtype "1" domino@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.0.tgz#653ba7d331441113b42e40ba05f24253ec86e02e" - integrity sha512-xINSODvrnuQcm3eXJN4IkBR+JxqLrJN8Ge4fd00y1b7HsY0A4huKN5BflSS/oo8quBWmocTfWdFvrw2H8TjGqQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.1.tgz#cd5c639940db72bb7cde1cdb5beea466a4113136" + integrity sha512-fqoTi6oQ881wYRENIEmz78hKVoc3X9HqVpklo419yxzebys6dtU5c83iVh3UYvvexPFdAuwlDYCsUM9//CrMMg== domutils@^1.5.1: version "1.7.0" @@ -3121,11 +3116,6 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1, duplexer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - duplexify@^3.2.0, duplexify@^3.5.0, duplexify@^3.6.0: version "3.6.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.1.tgz#b1a7a29c4abfd639585efaecce80d666b1e34125" @@ -3169,10 +3159,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.82: - version "1.3.85" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.85.tgz#5c46f790aa96445cabc57eb9d17346b1e46476fe" - integrity sha512-kWSDVVF9t3mft2OHVZy4K85X2beP6c6mFm3teFS/mLSDJpQwuFIWHrULCX+w6H1E55ZYmFRlT+ATAFRwhrYzsw== +electron-to-chromium@^1.3.86: + version "1.3.88" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz#f36ab32634f49ef2b0fdc1e82e2d1cc17feb29e7" + integrity sha512-UPV4NuQMKeUh1S0OWRvwg0PI8ASHN9kBC8yDTk1ROXLC85W5GnhTRu/MZu3Teqx3JjlQYuckuHYXSUSgtb3J+A== empower-core@^1.2.0: version "1.2.0" @@ -3455,19 +3445,6 @@ event-emitter@^0.3.5, event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" -event-stream@^3.3.4: - version "3.3.5" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.5.tgz#e5dd8989543630d94c6cf4d657120341fa31636b" - integrity sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g== - dependencies: - duplexer "^0.1.1" - from "^0.1.7" - map-stream "0.0.7" - pause-stream "^0.0.11" - split "^1.0.1" - stream-combiner "^0.2.2" - through "^2.3.8" - eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" @@ -3657,12 +3634,13 @@ eyes@0.1.x: integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= fancy-log@^1.1.0, fancy-log@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" - integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= + version "1.3.3" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" + integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== dependencies: ansi-gray "^0.1.1" color-support "^1.1.3" + parse-node-version "^1.0.0" time-stamp "^1.0.0" fast-deep-equal@^2.0.1: @@ -3930,14 +3908,14 @@ firebase@2.x.x: faye-websocket ">=0.6.0" firebase@^5.5.8: - version "5.5.9" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-5.5.9.tgz#1e20172d7c7dfafdc75a18378439e0493bc12753" - integrity sha512-IFABX9++5Bq7S00zYGdkdnqikq67cJuub26iyap4qNPnc05qXxx/5waomMIyEvfH74K7ywOaVWEy0E1BFNKk7g== + version "5.7.0" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-5.7.0.tgz#4e43e3f6521cfb4c1b4caf1b269a402c83713dd4" + integrity sha512-8sQYXCUbUuzpyZS+XpBogstQE/7lvX6tPWtrUH0Cf4mH7flH/Sue5GUvZzwBpkgKOrMpI+clzuoYm/ffGi/TjQ== dependencies: "@firebase/app" "0.3.5" - "@firebase/auth" "0.7.9" + "@firebase/auth" "0.9.0" "@firebase/database" "0.3.7" - "@firebase/firestore" "0.8.8" + "@firebase/firestore" "0.9.0" "@firebase/functions" "0.3.3" "@firebase/messaging" "0.3.7" "@firebase/polyfill" "0.3.3" @@ -3973,6 +3951,11 @@ flat-cache@^1.2.1: rimraf "~2.6.2" write "^0.2.1" +flatted@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" + integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" @@ -4048,11 +4031,6 @@ from2@^2.1.1: inherits "^2.0.1" readable-stream "^2.0.0" -from@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= - fs-access@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" @@ -4592,12 +4570,12 @@ google-p12-pem@^0.1.0: node-forge "^0.7.1" google-p12-pem@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-1.0.2.tgz#c8a3843504012283a0dbffc7430b7c753ecd4b07" - integrity sha512-+EuKr4CLlGsnXx4XIJIVkcKYrsa2xkAmCvxRhX2HsazJzUBAJ35wARGeApHUn4nNfPD03Vl057FskNr20VaCyg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-1.0.3.tgz#3d8acc140573339a5bca7b2f6a4b206bbea6d8d7" + integrity sha512-KGnAiMMWaJp4j4tYVvAjfP3wCKZRLv9M1Nir2wRRNWUYO7j1aX8O9Qgz+a8/EQ5rAvuo4SIu79n6SIdkNl7Msg== dependencies: - node-forge "^0.7.4" - pify "^3.0.0" + node-forge "^0.7.5" + pify "^4.0.0" google-proto-files@^0.16.0, google-proto-files@^0.16.1: version "0.16.1" @@ -4697,17 +4675,7 @@ graphviz@^0.0.8: dependencies: temp "~0.4.0" -grpc@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.16.0.tgz#cdf56d6ede2f1b6ab5224ad467cb22e1b3ec36fc" - integrity sha512-+p8YRIng7Gihkn2jycAXwXdA9aQ10SikRrcHY+/r3W1Z1Pr9NFIbLcmBZPoaTbzzLDv/ysqwqFEZriAdd8tveQ== - dependencies: - lodash "^4.17.5" - nan "^2.0.0" - node-pre-gyp "^0.10.0" - protobufjs "^5.0.3" - -grpc@^1.12.2: +grpc@1.16.1, grpc@^1.12.2: version "1.16.1" resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.16.1.tgz#533316f38cea68111ef577728c3f4e8e9e554543" integrity sha512-7uHN1Nd3UqfvwgQ6f5U3+EZb/0iuHJ9mbPH+ydaTkszJsUi3nwdz6DuSh0eJwYVXXn6Gojv2khiQAadMongGKg== @@ -4782,15 +4750,15 @@ gulp-cli@^2.0.1: yargs "^7.1.0" gulp-connect@^5.6.1: - version "5.6.1" - resolved "https://registry.yarnpkg.com/gulp-connect/-/gulp-connect-5.6.1.tgz#ba25bc7b1ec17e2fdae3a7368b510bf9edaa8991" - integrity sha512-FknHeA6smZhiRNrK/3UVH8BHLCFHS7WZdE7Y2VbZHtxye1UTIa5ZY0Cnst6O9n3kL8z7y43QI+acx3nUtJoiHw== + version "5.7.0" + resolved "https://registry.yarnpkg.com/gulp-connect/-/gulp-connect-5.7.0.tgz#7e925f5e4c34ebfedf9f318576966e8fe8840d5a" + integrity sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA== dependencies: ansi-colors "^2.0.5" connect "^3.6.6" connect-livereload "^0.6.0" - event-stream "^3.3.4" fancy-log "^1.3.2" + map-stream "^0.0.7" send "^0.16.2" serve-index "^1.9.1" serve-static "^1.13.2" @@ -6209,9 +6177,9 @@ karma-sourcemap-loader@^0.3.7: graceful-fs "^4.1.2" karma@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.1.tgz#94c8edd20fb9597ccde343326da009737fb0423a" - integrity sha512-NetT3wPCQMNB36uiL9LLyhrOt8SQwrEKt0xD3+KpTCfm0VxVyUJdPL5oTq2Ic5ouemgL/Iz4wqXEbF3zea9kQQ== + version "3.1.3" + resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.3.tgz#6e251648e3aff900927bc1126dbcbcb92d3edd61" + integrity sha512-JU4FYUtFEGsLZd6ZJzLrivcPj0TkteBiIRDcXWFsltPMGgZMDtby/MIzNOzgyZv/9dahs9vHpSxerC/ZfeX9Qw== dependencies: bluebird "^3.3.0" body-parser "^1.16.1" @@ -6223,11 +6191,12 @@ karma@^3.1.1: di "^0.0.1" dom-serialize "^2.2.0" expand-braces "^0.1.1" + flatted "^2.0.0" glob "^7.1.1" graceful-fs "^4.1.2" http-proxy "^1.13.0" isbinaryfile "^3.0.0" - lodash "^4.17.4" + lodash "^4.17.5" log4js "^3.0.0" mime "^2.3.1" minimatch "^3.0.2" @@ -6239,7 +6208,7 @@ karma@^3.1.1: socket.io "2.1.1" source-map "^0.6.1" tmp "0.0.33" - useragent "2.2.1" + useragent "2.3.0" keyv@3.0.0: version "3.0.0" @@ -6800,18 +6769,13 @@ lru-cache@2: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= -lru-cache@2.2.x: - version "2.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" - integrity sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0= - -lru-cache@^4.0.1, lru-cache@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.4.tgz#51cc46e8e6d9530771c857e24ccc720ecdbcc031" - integrity sha512-EPstzZ23znHUVLKj+lcXO1KvZkrlw+ZirdwvOmnAnA/1PB4ggyXJ77LRkCqkff+ShQ+cqoxCxLQOh4cKITO5iA== +lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== dependencies: pseudomap "^1.0.2" - yallist "^3.0.2" + yallist "^2.1.2" lru-queue@0.1: version "0.1.0" @@ -6886,7 +6850,7 @@ map-obj@^2.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= -map-stream@0.0.7: +map-stream@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg= @@ -7186,9 +7150,9 @@ minipass@^2.2.1, minipass@^2.3.4: yallist "^3.0.0" minizlib@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" - integrity sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg== + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== dependencies: minipass "^2.2.1" @@ -7380,7 +7344,7 @@ node-forge@0.7.4: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.4.tgz#8e6e9f563a1e32213aa7508cded22aa791dbf986" integrity sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA== -node-forge@^0.7.1, node-forge@^0.7.4: +node-forge@^0.7.1, node-forge@^0.7.5: version "0.7.6" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== @@ -7440,17 +7404,17 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" -node-releases@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.5.tgz#a641adcc968b039a27345d92ef10b093e5cbd41d" - integrity sha512-Ky7q0BO1BBkG/rQz6PkEZ59rwo+aSfhczHP1wwq8IowoVdN/FpiP7qp0XW0P2+BVCWe5fQUBozdbVd54q1RbCQ== +node-releases@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.0.tgz#be7464fa8d877808237520fd49436d5e79191c3d" + integrity sha512-+qV91QMDBvARuPxUEfI/mRF/BY+UAkTIn3pvmvM2iOLIRvv6RNYklFXBgrkky6P1wXUqQW1P3qKlWxxy4JZbfg== dependencies: semver "^5.3.0" node-sass@^4.8.3, node-sass@^4.9.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.10.0.tgz#dcc2b364c0913630945ccbf7a2bbf1f926effca4" - integrity sha512-fDQJfXszw6vek63Fe/ldkYXmRYK/QS6NbvM3i5oEo9ntPDy4XX7BcKZyTKv+/kSSxRtXXc7l+MSwEmYc0CSy6Q== + version "4.11.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" + integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -7943,6 +7907,11 @@ parse-ms@^2.0.0: resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.0.0.tgz#7b3640295100caf3fa0100ccceb56635b62f9d62" integrity sha512-AddiXFSLLCqj+tCRJ9MrUtHZB4DWojO3tk0NVZ+g5MaMQHF2+p2ktqxuoXyPFLljz/aUK0Nfhd/uGWnhXVXEyA== +parse-node-version@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.0.tgz#33d9aa8920dcc3c0d33658ec18ce237009a56d53" + integrity sha512-02GTVHD1u0nWc20n2G7WX/PgdhNFG04j5fi1OkaJzPWLTcf6vh6229Lta1wTmXG/7Dg42tCssgkccVt7qvd8Kg== + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -8081,13 +8050,6 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" -pause-stream@^0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= - dependencies: - through "~2.3" - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -8153,9 +8115,9 @@ pluralize@^7.0.0: integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== portfinder@^1.0.13: - version "1.0.19" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.19.tgz#07e87914a55242dcda5b833d42f018d6875b595f" - integrity sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw== + version "1.0.20" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a" + integrity sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw== dependencies: async "^1.5.2" debug "^2.2.0" @@ -8272,7 +8234,7 @@ postcss-values-parser@^1.5.0: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2, postcss@^7.0.3, postcss@^7.0.5: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2, postcss@^7.0.3, postcss@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.6.tgz#6dcaa1e999cdd4a255dcd7d4d9547f4ca010cdc2" integrity sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug== @@ -8452,9 +8414,9 @@ process-nextick-args@~1.0.6: integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= progress@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" - integrity sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg== + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== promise-polyfill@7.1.2: version "7.1.2" @@ -9700,9 +9662,9 @@ sparkles@^1.0.0: integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== spdx-correct@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" - integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ== + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" @@ -9764,7 +9726,7 @@ split2@^2.0.0: dependencies: through2 "^2.0.2" -split@^1.0.0, split@^1.0.1: +split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== @@ -9831,14 +9793,6 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stream-combiner@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" - integrity sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg= - dependencies: - duplexer "~0.1.1" - through "~2.3.4" - stream-consume@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" @@ -10330,7 +10284,7 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.2, through2@^2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4: +through@2, "through@>=2.2.7 <3", through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -10658,16 +10612,21 @@ typescript-eslint-parser@^18.0.0: lodash.unescape "4.0.1" semver "5.5.0" -typescript@^3.0.3, typescript@~3.1.1: - version "3.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" - integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== +typescript@^3.0.3: + version "3.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" + integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== typescript@~2.7.1: version "2.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" integrity sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw== +typescript@~3.1.1: + version "3.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" + integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== + uglify-js@3.4.x, uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" @@ -10960,12 +10919,12 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" -useragent@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e" - integrity sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4= +useragent@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" + integrity sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw== dependencies: - lru-cache "2.2.x" + lru-cache "4.1.x" tmp "0.0.x" util-deprecate@^1.0.1, util-deprecate@~1.0.1: @@ -11468,6 +11427,11 @@ y18n@^3.2.0, y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + yallist@^3.0.0, yallist@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"