From 605f4d16758e7921393760d0e1d0047f0ec0f3fa Mon Sep 17 00:00:00 2001 From: Adam Plumer Date: Fri, 16 Mar 2018 03:19:03 -0400 Subject: [PATCH] feat(tokens): add configuration for breakpoints and flex styles * add token to add individual breakpoints that can be merged with the defaults seamlessly * add token that optionally adds flex stylings to parents without inline flex styles (set to false by default) * add token to optionally disable adding vendor prefixes to inline styles BREAKING CHANGE: * `fxFlex` no longer adds `display: flex; flex-direction: row` by default --- docs/documentation/BreakPoints.md | 52 +++- .../core/breakpoints/break-point-registry.ts | 4 +- .../core/breakpoints/break-points-provider.ts | 57 +++- .../core/breakpoints/breakpoint-tools.spec.ts | 35 ++- src/lib/core/breakpoints/breakpoint-tools.ts | 24 +- .../data/orientation-break-points.spec.ts | 27 +- src/lib/core/match-media/match-media.spec.ts | 46 ++-- .../match-media/mock/mock-match-media.spec.ts | 49 ++-- .../core/media-monitor/media-monitor.spec.ts | 4 +- src/lib/core/module.ts | 4 +- .../observable-media/observable-media.spec.ts | 245 +++++++++--------- src/lib/core/public-api.ts | 2 +- .../responsive-activation.spec.ts | 70 +++-- src/lib/core/style-utils/style-utils.ts | 10 +- src/lib/core/tokens/breakpoint-token.ts | 18 ++ src/lib/core/tokens/flex-styles-token.ts | 11 + src/lib/core/tokens/index.ts | 12 + src/lib/core/{ => tokens}/server-token.ts | 0 src/lib/core/tokens/vendor-prefixes-token.ts | 11 + src/lib/extended/class/class.spec.ts | 12 +- src/lib/extended/img-src/img-src.spec.ts | 6 +- src/lib/extended/show-hide/hide.spec.ts | 9 +- src/lib/extended/show-hide/show.spec.ts | 9 +- src/lib/extended/style/style.spec.ts | 11 +- src/lib/flex/flex-offset/flex-offset.spec.ts | 12 +- src/lib/flex/flex/flex.spec.ts | 94 ++++++- src/lib/flex/flex/flex.ts | 14 +- .../flex/layout-align/layout-align.spec.ts | 8 +- src/lib/flex/layout-gap/layout-gap.spec.ts | 13 +- src/lib/flex/layout/layout.spec.ts | 7 +- 30 files changed, 517 insertions(+), 359 deletions(-) create mode 100644 src/lib/core/tokens/breakpoint-token.ts create mode 100644 src/lib/core/tokens/flex-styles-token.ts create mode 100644 src/lib/core/tokens/index.ts rename src/lib/core/{ => tokens}/server-token.ts (100%) create mode 100644 src/lib/core/tokens/vendor-prefixes-token.ts diff --git a/docs/documentation/BreakPoints.md b/docs/documentation/BreakPoints.md index ad18dfee2..ed2b3a0ea 100644 --- a/docs/documentation/BreakPoints.md +++ b/docs/documentation/BreakPoints.md @@ -23,20 +23,21 @@ export class CoreModule { } ``` -This provider is used to return a list to *all* known BreakPoint(s)... and, in turn, this list is used internally to +This provider is used to return a list to *all* known BreakPoint(s) and, in turn, this list is used internally to register mediaQueries and announce mediaQuery activations. ### Custom BreakPoints -Using the **BREAKPOINTS** InjectionToken, developers can add custom breakpoints or easily override existing breakpoints. +Using the **BREAKPOINT** (note: singular) `InjectionToken`, developers can add custom breakpoints or easily override +existing breakpoints. For example to add mediaQueries that activate when printing: ##### `custom-breakpoints.ts` ```typescript -import {BREAKPOINTS, DEFAULT_BREAKPOINTS} from '@angular/flex-layout'; +import {BREAKPOINT} from '@angular/flex-layout'; const PRINT_BREAKPOINTS = [{ alias: 'xs.print', @@ -46,8 +47,9 @@ const PRINT_BREAKPOINTS = [{ }]; export const CustomBreakPointsProvider = { - provide: BREAKPOINTS, - useValue: [...DEFAULT_BREAKPOINTS, ...PRINT_BREAKPOINTS] + provide: BREAKPOINT, + useValue: PRINT_BREAKPOINTS, + multi: true }; ``` @@ -56,7 +58,7 @@ export const CustomBreakPointsProvider = { ```typescript import {CommonModule, NgModule} from '@angular/core'; import {FlexLayoutModule} from '@angular/flex-layout'; -import {CustomBreakPointsProvider} from 'custom-breakpoints.ts'; +import {CustomBreakPointsProvider} from './custom-breakpoints.ts'; @NgModule({ imports : [ @@ -71,7 +73,43 @@ export class MyAppModule { } ``` -With the above changes, when printing on mobile-sized viewports the **`xs.print`** mediaQuery will activate. +With the above changes, when printing on mobile-sized viewports the **`xs.print`** mediaQuery will activate. Please note +that the provider is a **multi-provider**, meaning it can be provided multiple times and in a variety of +presentations. The type signature of `BREAKPOINT` is the following: + +`BREAKPOINT = InjectionToken` + +Thus, you can use the token to segment which breakpoints you provide and in which order. For instance, +you can provide all print breakpoints in an array called `PRINT_BREAKPOINTS` and then all mobile breakpoints +in another array called `MOBILE_BREAKPOINTS`. You can also simply provide one additional breakpoint if that's +all you need. + +### Disabling the default breakpoints + +To disable the default breakpoints, you simply provide the new **DISABLE_DEFAULT_BREAKPOINTS** token as follows: + +```typescript +import {DISABLE_DEFAULT_BREAKPOINTS} from '@angular/flex-layout'; + +{provide: DISABLE_DEFAULT_BREAKPOINTS, useValue: true} +``` + +The default value for this breakpoint is false + +### Adding the orientation breakpoints + +The orientation breakpoints are a set of breakpoints that detect when a device is in portrait or landscape mode. Flex +Layout has a set of these that conform to the Material Design spec built-in to the library. They can be found in the +`ORIENTATION_BREAKPOINTS` `InjectionToken`. To have these added to the default breakpoints, you can provide the token +`ADD_ORIENTATION_BREAKPOINTS` to your app as follows: + +```typescript +import {ADD_ORIENTATION_BREAKPOINTS} from '@angular/flex-layout'; + +{provide: ADD_ORIENTATION_BREAKPOINTS, useValue: true} +``` + +The default value for this breakpoint is false ### Custom Breakpoints and Directives diff --git a/src/lib/core/breakpoints/break-point-registry.ts b/src/lib/core/breakpoints/break-point-registry.ts index 5c69354e3..8dac867e2 100644 --- a/src/lib/core/breakpoints/break-point-registry.ts +++ b/src/lib/core/breakpoints/break-point-registry.ts @@ -13,7 +13,7 @@ import {BREAKPOINTS} from './break-points-token'; /** * Registry of 1..n MediaQuery breakpoint ranges - * This is published as a provider and may be overriden from custom, application-specific ranges + * This is published as a provider and may be overridden from custom, application-specific ranges * */ @Injectable() @@ -25,7 +25,7 @@ export class BreakPointRegistry { /** * Accessor to raw list */ - get items(): BreakPoint[ ] { + get items(): BreakPoint[] { return [...this._registry]; } diff --git a/src/lib/core/breakpoints/break-points-provider.ts b/src/lib/core/breakpoints/break-points-provider.ts index 7340392c7..1d64eb82d 100644 --- a/src/lib/core/breakpoints/break-points-provider.ts +++ b/src/lib/core/breakpoints/break-points-provider.ts @@ -5,20 +5,26 @@ * 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 {InjectionToken} from '@angular/core'; +import {InjectionToken, Optional, SkipSelf} from '@angular/core'; import {BreakPoint} from './break-point'; import {BREAKPOINTS} from './break-points-token'; import {DEFAULT_BREAKPOINTS} from './data/break-points'; import {ORIENTATION_BREAKPOINTS} from './data/orientation-break-points'; - import {extendObject} from '../../utils/object-extend'; import {mergeByAlias, validateSuffixes} from './breakpoint-tools'; +import { + ADD_ORIENTATION_BREAKPOINTS, + BREAKPOINT, + DISABLE_DEFAULT_BREAKPOINTS, +} from '../tokens/breakpoint-token'; /** * Options to identify which breakpoint types to include as part of * a BreakPoint provider + * @deprecated + * @deletion-target v6.0.0-beta.15 */ export interface BreakPointProviderOptions { /** @@ -35,11 +41,13 @@ export interface BreakPointProviderOptions { /** * Add new custom items to the default list or override existing default with custom overrides + * @deprecated + * @deletion-target v6.0.0-beta.15 */ export function buildMergedBreakPoints(_custom?: BreakPoint[], options?: BreakPointProviderOptions) { options = extendObject({}, { - defaults: true, // exclude pre-configured, internal default breakpoints + defaults: true, // exclude pre-configured, internal default breakpoints orientation: false // exclude pre-configured, internal orientations breakpoints }, options || {}); @@ -55,6 +63,8 @@ export function buildMergedBreakPoints(_custom?: BreakPoint[], /** * Ensure that only a single global BreakPoint list is instantiated... + * @deprecated + * @deletion-target v6.0.0-beta.15 */ export function DEFAULT_BREAKPOINTS_PROVIDER_FACTORY() { return validateSuffixes(DEFAULT_BREAKPOINTS); @@ -67,18 +77,53 @@ export function DEFAULT_BREAKPOINTS_PROVIDER_FACTORY() { * custom breakpoints matching existing breakpoints will override the properties * of the existing (and not be added as an extra breakpoint entry). * [xs, gt-xs, sm, gt-sm, md, gt-md, lg, gt-lg, xl] + * @deprecated + * @deletion-target v6.0.0-beta.15 */ -export const DEFAULT_BREAKPOINTS_PROVIDER = { // tslint:disable-line:variable-name +export const DEFAULT_BREAKPOINTS_PROVIDER = { provide: BREAKPOINTS, useFactory: DEFAULT_BREAKPOINTS_PROVIDER_FACTORY }; + +/** + * Factory that combines the configured breakpoints into one array and then merges + * them using a utility function + */ +export function BREAKPOINTS_PROVIDER_FACTORY(parentBreakpoints: BreakPoint[], + breakpoints: (BreakPoint|BreakPoint[])[], + disableDefaults: boolean, + addOrientation: boolean) { + const bpFlattenArray = [].concat.apply([], (breakpoints || []) + .map(v => Array.isArray(v) ? v : [v])); + const builtIns = DEFAULT_BREAKPOINTS.concat(addOrientation ? ORIENTATION_BREAKPOINTS : []); + return parentBreakpoints || disableDefaults ? + mergeByAlias(bpFlattenArray) : mergeByAlias(builtIns, bpFlattenArray); +} + +/** + * Provider that combines the provided extra breakpoints with the default and + * orientation breakpoints based on configuration + */ +export const BREAKPOINTS_PROVIDER = { + provide: BREAKPOINTS, + useFactory: BREAKPOINTS_PROVIDER_FACTORY, + deps: [ + [new Optional(), new SkipSelf(), BREAKPOINTS], + [new Optional(), BREAKPOINT], + [new Optional(), DISABLE_DEFAULT_BREAKPOINTS], + [new Optional(), ADD_ORIENTATION_BREAKPOINTS], + ] +}; + /** * Use with FlexLayoutModule.CUSTOM_BREAKPOINTS_PROVIDER_FACTORY! + * @deprecated + * @deletion-target v6.0.0-beta.15 */ -export function CUSTOM_BREAKPOINTS_PROVIDER_FACTORY(_custom?: BreakPoint[], +export function CUSTOM_BREAKPOINTS_PROVIDER_FACTORY(custom?: BreakPoint[], options?: BreakPointProviderOptions) { return { provide: >BREAKPOINTS, - useFactory: buildMergedBreakPoints(_custom, options) + useFactory: buildMergedBreakPoints(custom, options) }; } diff --git a/src/lib/core/breakpoints/breakpoint-tools.spec.ts b/src/lib/core/breakpoints/breakpoint-tools.spec.ts index 90ebf7a14..419f0076a 100644 --- a/src/lib/core/breakpoints/breakpoint-tools.spec.ts +++ b/src/lib/core/breakpoints/breakpoint-tools.spec.ts @@ -5,14 +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 {TestBed, inject} from '@angular/core/testing'; +import {TestBed, inject, fakeAsync} from '@angular/core/testing'; import {BreakPoint} from './break-point'; import {BREAKPOINTS} from './break-points-token'; -import { - DEFAULT_BREAKPOINTS_PROVIDER, - buildMergedBreakPoints -} from './break-points-provider'; +import {BREAKPOINTS_PROVIDER, BREAKPOINTS_PROVIDER_FACTORY} from './break-points-provider'; import {validateSuffixes, mergeByAlias} from './breakpoint-tools'; describe('breakpoint-tools', () => { @@ -22,7 +19,7 @@ describe('breakpoint-tools', () => { }, null); beforeEach(() => { - all = buildMergedBreakPoints([], {orientations: true})(); + all = BREAKPOINTS_PROVIDER_FACTORY([], [], false, true); }); describe('validation', () => { @@ -47,18 +44,20 @@ describe('breakpoint-tools', () => { expect(validated[4].suffix).toEqual('HandsetPortrait'); }); it('should auto-validate the DEFAULT_BREAKPOINTS', () => { - let xsBp: BreakPoint = findByAlias('xs')!; - let gtLgBp: BreakPoint = findByAlias('gt-lg')!; - let xlBp: BreakPoint = findByAlias('xl')!; + fakeAsync(() => { + let xsBp: BreakPoint = findByAlias('xs')!; + let gtLgBp: BreakPoint = findByAlias('gt-lg')!; + let xlBp: BreakPoint = findByAlias('xl')!; - expect(xsBp.alias).toEqual('xs'); - expect(xsBp.suffix).toEqual('Xs'); + expect(xsBp.alias).toEqual('xs'); + expect(xsBp.suffix).toEqual('Xs'); - expect(gtLgBp.alias).toEqual('gt-lg'); - expect(gtLgBp.suffix).toEqual('GtLg'); + expect(gtLgBp.alias).toEqual('gt-lg'); + expect(gtLgBp.suffix).toEqual('GtLg'); - expect(xlBp.alias).toEqual('xl'); - expect(xlBp.suffix).toEqual('Xl'); + expect(xlBp.alias).toEqual('xl'); + expect(xlBp.suffix).toEqual('Xl'); + }); }); }); @@ -76,7 +75,7 @@ describe('breakpoint-tools', () => { }); it('should add custom breakpoints with unique aliases', () => { let defaults = [{alias: 'xs', mediaQuery: 'screen and (max-width: 599px)'}], - custom = [{alias: 'sm', mediaQuery: 'screen'}, {alias: 'md', mediaQuery: 'screen'}]; + custom = [{alias: 'sm', mediaQuery: 'screen'}, {alias: 'md', mediaQuery: 'screen'}]; all = mergeByAlias(defaults, custom); @@ -97,12 +96,12 @@ describe('breakpoint-tools', () => { }); }); - describe('with DEFAULT_BREAKPOINTS_PROVIDER', () => { + describe('with BREAKPOINTS_PROVIDER', () => { beforeEach(() => { // Configure testbed to prepare services TestBed.configureTestingModule({ providers: [ - DEFAULT_BREAKPOINTS_PROVIDER // Supports developer overrides of list of known breakpoints + BREAKPOINTS_PROVIDER // Supports developer overrides of list of known breakpoints ] }); }); diff --git a/src/lib/core/breakpoints/breakpoint-tools.ts b/src/lib/core/breakpoints/breakpoint-tools.ts index f39fd9d19..6a9a9aa5c 100644 --- a/src/lib/core/breakpoints/breakpoint-tools.ts +++ b/src/lib/core/breakpoints/breakpoint-tools.ts @@ -34,9 +34,9 @@ function camelCase(name: string): string { */ export function validateSuffixes(list: BreakPoint[]): BreakPoint[] { list.forEach((bp: BreakPoint) => { - if (!bp.suffix || bp.suffix === '') { - bp.suffix = camelCase(bp.alias); // create Suffix value based on alias - bp.overlapping = bp.overlapping || false; // ensure default value + if (!bp.suffix) { + bp.suffix = camelCase(bp.alias); // create Suffix value based on alias + bp.overlapping = !!bp.overlapping; // ensure default value } }); return list; @@ -48,21 +48,19 @@ export function validateSuffixes(list: BreakPoint[]): BreakPoint[] { * - Items are merged with the custom override if the alias exists in the default list */ export function mergeByAlias(defaults: BreakPoint[], custom: BreakPoint[] = []): BreakPoint[] { - const merged = defaults.map((bp) => extendObject({}, bp)); - const findByAlias = (alias) => merged.reduce((result, bp) => { - return result || (( bp.alias === alias) ? bp : null); - }, null); - + const dict: {[key: string]: BreakPoint} = {}; + defaults.forEach(bp => { + dict[bp.alias] = bp; + }); // Merge custom breakpoints custom.forEach((bp: BreakPoint) => { - let target = findByAlias(bp.alias); - if (target) { - extendObject(target, bp); + if (dict[bp.alias]) { + extendObject(dict[bp.alias], bp); } else { - merged.push(bp); + dict[bp.alias] = bp; } }); - return validateSuffixes(merged); + return validateSuffixes(Object.keys(dict).map(k => dict[k])); } diff --git a/src/lib/core/breakpoints/data/orientation-break-points.spec.ts b/src/lib/core/breakpoints/data/orientation-break-points.spec.ts index 95033e499..106101f54 100644 --- a/src/lib/core/breakpoints/data/orientation-break-points.spec.ts +++ b/src/lib/core/breakpoints/data/orientation-break-points.spec.ts @@ -12,10 +12,12 @@ import {BreakPoint} from '../break-point'; import {DEFAULT_BREAKPOINTS} from './break-points'; import {ORIENTATION_BREAKPOINTS} from './orientation-break-points'; import {BREAKPOINTS} from '../break-points-token'; +import {BREAKPOINTS_PROVIDER} from '../break-points-provider'; import { - DEFAULT_BREAKPOINTS_PROVIDER, - CUSTOM_BREAKPOINTS_PROVIDER_FACTORY -} from '../break-points-provider'; + ADD_ORIENTATION_BREAKPOINTS, + BREAKPOINT, + DISABLE_DEFAULT_BREAKPOINTS, +} from '../../tokens/breakpoint-token'; describe('break-point-provider', () => { let breakPoints: BreakPoint[]; @@ -27,7 +29,7 @@ describe('break-point-provider', () => { beforeEach(() => { // Configure testbed to prepare services TestBed.configureTestingModule({ - providers: [DEFAULT_BREAKPOINTS_PROVIDER] + providers: [BREAKPOINTS_PROVIDER] }); }); beforeEach(async(inject([BREAKPOINTS], (_) => { @@ -53,7 +55,10 @@ describe('break-point-provider', () => { beforeEach(() => { // Configure testbed to prepare services TestBed.configureTestingModule({ - providers: [CUSTOM_BREAKPOINTS_PROVIDER_FACTORY(EXTRAS)] + providers: [ + BREAKPOINTS_PROVIDER, + {provide: BREAKPOINT, useValue: EXTRAS, multi: true}, + ] }); }); // tslint:disable-next-line:no-shadowed-variable @@ -90,7 +95,11 @@ describe('break-point-provider', () => { beforeEach(() => { // Configure testbed to prepare services TestBed.configureTestingModule({ - providers: [CUSTOM_BREAKPOINTS_PROVIDER_FACTORY(EXTRAS, {orientations: true})] + providers: [ + BREAKPOINTS_PROVIDER, + {provide: BREAKPOINT, useValue: EXTRAS, multi: true}, + {provide: ADD_ORIENTATION_BREAKPOINTS, useValue: true}, + ] }); }); // tslint:disable-next-line:no-shadowed-variable @@ -131,7 +140,11 @@ describe('break-point-provider', () => { beforeEach(() => { // Configure testbed to prepare services TestBed.configureTestingModule({ - providers: [CUSTOM_BREAKPOINTS_PROVIDER_FACTORY(EXTRAS, {defaults: false})] + providers: [ + BREAKPOINTS_PROVIDER, + {provide: BREAKPOINT, useValue: EXTRAS, multi: true}, + {provide: DISABLE_DEFAULT_BREAKPOINTS, useValue: true}, + ] }); }); // tslint:disable-next-line:no-shadowed-variable diff --git a/src/lib/core/match-media/match-media.spec.ts b/src/lib/core/match-media/match-media.spec.ts index 7ffc69d38..0fab478ff 100644 --- a/src/lib/core/match-media/match-media.spec.ts +++ b/src/lib/core/match-media/match-media.spec.ts @@ -11,8 +11,8 @@ import {TestBed, inject, async} from '@angular/core/testing'; import {MediaChange} from '../media-change'; import {BreakPoint} from '../breakpoints/break-point'; -import {MockMatchMedia} from './mock/mock-match-media'; -import {DEFAULT_BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; +import {MockMatchMedia, MockMatchMediaProvider} from './mock/mock-match-media'; +import {BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; import {BreakPointRegistry} from '../breakpoints/break-point-registry'; import {MatchMedia} from './match-media'; import {ObservableMedia} from '../observable-media/observable-media'; @@ -26,7 +26,7 @@ describe('match-media', () => { TestBed.configureTestingModule({ providers: [ BreakPointRegistry, // Registry of known/used BreakPoint(s) - DEFAULT_BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints + BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints {provide: MatchMedia, useClass: MockMatchMedia} ] }); @@ -43,10 +43,10 @@ describe('match-media', () => { it('can observe the initial, default activation for mediaQuery == "all". ', () => { let current: MediaChange; let subscription = matchMedia - .observe() - .subscribe((change: MediaChange) => { - current = change; - }); + .observe() + .subscribe((change: MediaChange) => { + current = change; + }); async(() => { expect(current.mediaQuery).toEqual('all'); @@ -69,14 +69,12 @@ describe('match-media', () => { async(() => { expect(current.mediaQuery).toEqual('all'); // default mediaQuery is active - let activated = matchMedia.activate(query1); // simulate mediaQuery change to Query1 expect(activated).toEqual(true); expect(current.mediaQuery).toEqual(query1); expect(matchMedia.isActive(query1)).toBeTruthy(); activated = matchMedia.activate(query2); // simulate mediaQuery change to Query2 - expect(activated).toEqual(true); expect(current.mediaQuery).toEqual(query2); // confirm no notification expect(matchMedia.isActive(query2)).toBeTruthy(); @@ -99,15 +97,12 @@ describe('match-media', () => { async(() => { expect(current).toBeUndefined(); // no notification for the default, active mediaQuery - activated = matchMedia.activate(query1); // simulate mediaQuery change - expect(activated).toEqual(true); expect(current.mediaQuery).toEqual(query1); expect(matchMedia.isActive(query1)).toBeTruthy(); activated = matchMedia.activate(query2); // simulate mediaQuery change - expect(activated).toEqual(true); expect(matchMedia.isActive(query2)).toBeTruthy(); @@ -130,27 +125,27 @@ describe('match-media-observable', () => { // Configure testbed to prepare services TestBed.configureTestingModule({ providers: [ - DEFAULT_BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints + BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints BreakPointRegistry, // Registry of known/used BreakPoint(s) OBSERVABLE_MEDIA_PROVIDER, // injectable `media$` matchMedia observable - {provide: MatchMedia, useClass: MockMatchMedia} + MockMatchMediaProvider, ] }); }); // Single async inject to save references; which are used in all tests below beforeEach(async(inject( - [ObservableMedia, MatchMedia, BreakPointRegistry], - (_media$, _matchMedia, _breakPoints) => { - matchMedia = _matchMedia; // inject only to manually activate mediaQuery ranges - breakPoints = _breakPoints; - mediaQuery$ = _media$; - - // Quick register all breakpoint mediaQueries - breakPoints.items.forEach((bp: BreakPoint) => { - matchMedia.observe(bp.mediaQuery); - }); - }))); + [ObservableMedia, MatchMedia, BreakPointRegistry], + (_media$, _matchMedia, _breakPoints) => { + matchMedia = _matchMedia; // inject only to manually activate mediaQuery ranges + breakPoints = _breakPoints; + mediaQuery$ = _media$; + + // Quick register all breakpoint mediaQueries + breakPoints.items.forEach((bp: BreakPoint) => { + matchMedia.observe(bp.mediaQuery); + }); + }))); afterEach(() => { matchMedia.clearAll(); }); @@ -235,7 +230,6 @@ describe('match-media-observable', () => { matchMedia.activate(breakPoints.findByAlias('gt-md')!.mediaQuery); // 'all' mediaQuery is already active; total count should be (3) - expect(activationCount).toEqual(2); expect(deactivationCount).toEqual(0); diff --git a/src/lib/core/match-media/mock/mock-match-media.spec.ts b/src/lib/core/match-media/mock/mock-match-media.spec.ts index 1f98fcf43..e00f4b5c5 100644 --- a/src/lib/core/match-media/mock/mock-match-media.spec.ts +++ b/src/lib/core/match-media/mock/mock-match-media.spec.ts @@ -11,7 +11,7 @@ import {TestBed, inject, async} from '@angular/core/testing'; import {MediaChange} from '../../media-change'; import {BreakPoint} from '../../breakpoints/break-point'; import {MockMatchMedia} from './mock-match-media'; -import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../breakpoints/break-points-provider'; +import {BREAKPOINTS_PROVIDER} from '../../breakpoints/break-points-provider'; import {BreakPointRegistry} from '../../breakpoints/break-point-registry'; describe('mock-match-media', () => { @@ -24,7 +24,7 @@ describe('mock-match-media', () => { providers: [ MockMatchMedia, BreakPointRegistry, // Registry of known/used BreakPoint(s) - DEFAULT_BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints + BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints ] }); }); @@ -45,9 +45,9 @@ describe('mock-match-media', () => { let current: MediaChange; let customQuery = 'screen and (min-width: 610px) and (max-width: 620px'; let subscription = matchMedia.observe(customQuery) - .subscribe((change: MediaChange) => { - current = change; - }); + .subscribe((change: MediaChange) => { + current = change; + }); let activated = matchMedia.activate(customQuery); expect(activated).toEqual(true); @@ -77,9 +77,9 @@ describe('mock-match-media', () => { it('can observe ALL media query changes', () => { let current: MediaChange, - mqcGtSM: MediaChange, - bpGtSM = breakPoints.findByAlias('gt-sm'), - bpLg = breakPoints.findByAlias('lg'); + mqcGtSM: MediaChange, + bpGtSM = breakPoints.findByAlias('gt-sm'), + bpLg = breakPoints.findByAlias('lg'); let subscription = matchMedia.observe().subscribe((change: MediaChange) => { current = change; @@ -105,8 +105,8 @@ describe('mock-match-media', () => { it('can observe only a specific media query changes', () => { let current: MediaChange, - bpGtSM = breakPoints.findByAlias('gt-sm'), - bpLg = breakPoints.findByAlias('lg'); + bpGtSM = breakPoints.findByAlias('gt-sm'), + bpLg = breakPoints.findByAlias('lg'); let subscription = matchMedia.observe(bpLg!.mediaQuery).subscribe((change: MediaChange) => { current = change; @@ -129,10 +129,9 @@ describe('mock-match-media', () => { it('can observe both activation and deactivation changes', () => { let activates = 0, deactivates = 0; let bpGtSM = breakPoints.findByAlias('gt-sm'), - bpLg = breakPoints.findByAlias('lg'); + bpLg = breakPoints.findByAlias('lg'); // By default the 'all' is initially active. - let subscription = matchMedia.observe().subscribe((change: MediaChange) => { if (change.matches) { ++activates; @@ -161,7 +160,7 @@ describe('mock-match-media', () => { it('can observe both activated & deactivated changes for specific mediaQueries', () => { let activates = 0, deactivates = 0; let bpGtSM = breakPoints.findByAlias('gt-sm'), - bpLg = breakPoints.findByAlias('lg'); + bpLg = breakPoints.findByAlias('lg'); let subscription = matchMedia.observe(bpGtSM!.mediaQuery).subscribe((change: MediaChange) => { if (change.matches) { @@ -172,7 +171,6 @@ describe('mock-match-media', () => { }); expect(activates).toEqual(0); // from alias == '' == 'all' - matchMedia.activate(bpGtSM!.mediaQuery); expect(activates).toEqual(1); expect(deactivates).toEqual(0); @@ -191,7 +189,7 @@ describe('mock-match-media', () => { it('can activate with either a mediaQuery or an alias', () => { let activates = 0; let bpGtSM = breakPoints.findByAlias('gt-sm'), - bpLg = breakPoints.findByAlias('lg'); + bpLg = breakPoints.findByAlias('lg'); let subscription = matchMedia.observe().subscribe((change: MediaChange) => { if (change.matches) { @@ -200,7 +198,6 @@ describe('mock-match-media', () => { }); expect(activates).toEqual(1); // from alias == '' == 'all' - matchMedia.activate(bpGtSM!.mediaQuery); expect(activates).toEqual(2); @@ -218,12 +215,12 @@ describe('mock-match-media', () => { it('can check if a range is active', () => { let bpXs = breakPoints.findByAlias('xs'), - bpGtXs = breakPoints.findByAlias('gt-xs'), - bpSm = breakPoints.findByAlias('sm'), - bpGtSm = breakPoints.findByAlias('gt-sm'), - bpMd = breakPoints.findByAlias('md'), - bpGtMd = breakPoints.findByAlias('gt-md'), - bpLg = breakPoints.findByAlias('lg'); + bpGtXs = breakPoints.findByAlias('gt-xs'), + bpSm = breakPoints.findByAlias('sm'), + bpGtSm = breakPoints.findByAlias('gt-sm'), + bpMd = breakPoints.findByAlias('md'), + bpGtMd = breakPoints.findByAlias('gt-md'), + bpLg = breakPoints.findByAlias('lg'); let subscription = matchMedia.observe().subscribe(() => { }); @@ -249,13 +246,13 @@ describe('mock-match-media', () => { it('can observe a startup activation of XS', () => { let current: MediaChange, - bpXS = breakPoints.findByAlias('xs'); + bpXS = breakPoints.findByAlias('xs'); matchMedia.activate(bpXS!.mediaQuery); let subscription = matchMedia.observe(bpXS!.mediaQuery) - .subscribe((change: MediaChange) => { - current = change; - }); + .subscribe((change: MediaChange) => { + current = change; + }); async(() => { expect(current).toBeTruthy(); diff --git a/src/lib/core/media-monitor/media-monitor.spec.ts b/src/lib/core/media-monitor/media-monitor.spec.ts index 7882e54e6..db7f9a36d 100644 --- a/src/lib/core/media-monitor/media-monitor.spec.ts +++ b/src/lib/core/media-monitor/media-monitor.spec.ts @@ -7,7 +7,7 @@ */ import {TestBed, inject, async} from '@angular/core/testing'; -import {DEFAULT_BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; +import {BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; import {DEFAULT_BREAKPOINTS} from '../breakpoints/data/break-points'; import {MediaChange} from '../media-change'; import {MockMatchMedia} from '../match-media/mock/mock-match-media'; @@ -27,7 +27,7 @@ describe('media-monitor', () => { providers: [ MediaMonitor, BreakPointRegistry, // Registry of known/used BreakPoint(s) - DEFAULT_BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints + BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints {provide: MatchMedia, useClass: MockMatchMedia} ] }); diff --git a/src/lib/core/module.ts b/src/lib/core/module.ts index 534bc1762..603a37819 100644 --- a/src/lib/core/module.ts +++ b/src/lib/core/module.ts @@ -11,7 +11,7 @@ import {MediaMonitor} from './media-monitor/media-monitor'; import {BreakPointRegistry} from './breakpoints/break-point-registry'; import {OBSERVABLE_MEDIA_PROVIDER} from './observable-media/observable-media-provider'; -import {DEFAULT_BREAKPOINTS_PROVIDER} from './breakpoints/break-points-provider'; +import {BREAKPOINTS_PROVIDER} from './breakpoints/break-points-provider'; import {MATCH_MEDIA_PROVIDER} from './match-media/match-media-provider'; import {BROWSER_PROVIDER} from './browser-provider'; import {StyleUtils} from './style-utils/style-utils'; @@ -25,7 +25,7 @@ import {STYLESHEET_MAP_PROVIDER} from './stylesheet-map/stylesheet-map-provider' @NgModule({ providers: [ - DEFAULT_BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints + BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints BreakPointRegistry, // Registry of known/used BreakPoint(s) MATCH_MEDIA_PROVIDER, // Low-level service to publish observables w/ window.matchMedia() MediaMonitor, // MediaQuery monitor service observes all known breakpoints diff --git a/src/lib/core/observable-media/observable-media.spec.ts b/src/lib/core/observable-media/observable-media.spec.ts index c5fa6e5c7..272156e87 100644 --- a/src/lib/core/observable-media/observable-media.spec.ts +++ b/src/lib/core/observable-media/observable-media.spec.ts @@ -12,15 +12,13 @@ import {map} from 'rxjs/operators/map'; import {BreakPoint} from '../breakpoints/break-point'; import {BREAKPOINTS} from '../breakpoints/break-points-token'; import {BreakPointRegistry} from '../breakpoints/break-point-registry'; -import { - DEFAULT_BREAKPOINTS_PROVIDER, - CUSTOM_BREAKPOINTS_PROVIDER_FACTORY -} from '../breakpoints/break-points-provider'; +import {BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; import {MatchMedia} from '../match-media/match-media'; import {MediaChange} from '../media-change'; import {ObservableMedia} from './observable-media'; import {OBSERVABLE_MEDIA_PROVIDER} from './observable-media-provider'; import {MockMatchMediaProvider} from '../match-media/mock/mock-match-media'; +import {BREAKPOINT} from '../tokens/breakpoint-token'; describe('observable-media', () => { @@ -36,7 +34,7 @@ describe('observable-media', () => { // Configure testbed to prepare services TestBed.configureTestingModule({ providers: [ - DEFAULT_BREAKPOINTS_PROVIDER, + BREAKPOINTS_PROVIDER, BreakPointRegistry, // Registry of known/used BreakPoint(s) MockMatchMediaProvider, OBSERVABLE_MEDIA_PROVIDER, @@ -49,141 +47,140 @@ describe('observable-media', () => { })); it('can supports the `.isActive()` API', async(inject( - [ObservableMedia, MatchMedia], - (media, matchMedia) => { - expect(media).toBeDefined(); + [ObservableMedia, MatchMedia], + (media, matchMedia) => { + expect(media).toBeDefined(); - // Activate mediaQuery associated with 'md' alias - matchMedia.activate('md'); - expect(media.isActive('md')).toBeTruthy(); + // Activate mediaQuery associated with 'md' alias + matchMedia.activate('md'); + expect(media.isActive('md')).toBeTruthy(); - matchMedia.activate('lg'); - expect(media.isActive('lg')).toBeTruthy(); - expect(media.isActive('md')).toBeFalsy(); + matchMedia.activate('lg'); + expect(media.isActive('lg')).toBeTruthy(); + expect(media.isActive('md')).toBeFalsy(); - }))); + }))); it('can supports RxJS operators', inject( - [ObservableMedia, MatchMedia], - (mediaService, matchMedia) => { - let count = 0, - subscription = mediaService.asObservable().pipe( - filter((change: MediaChange) => change.mqAlias == 'md'), - map((change: MediaChange) => change.mqAlias) - ).subscribe(_ => { - count += 1; - }); + [ObservableMedia, MatchMedia], + (mediaService, matchMedia) => { + let count = 0, + subscription = mediaService.asObservable().pipe( + filter((change: MediaChange) => change.mqAlias == 'md'), + map((change: MediaChange) => change.mqAlias) + ).subscribe(_ => { + count += 1; + }); - // Activate mediaQuery associated with 'md' alias - matchMedia.activate('sm'); - expect(count).toEqual(0); + // Activate mediaQuery associated with 'md' alias + matchMedia.activate('sm'); + expect(count).toEqual(0); - matchMedia.activate('md'); - expect(count).toEqual(1); + matchMedia.activate('md'); + expect(count).toEqual(1); - matchMedia.activate('lg'); - expect(count).toEqual(1); + matchMedia.activate('lg'); + expect(count).toEqual(1); - matchMedia.activate('md'); - expect(count).toEqual(2); + matchMedia.activate('md'); + expect(count).toEqual(2); - matchMedia.activate('gt-md'); - matchMedia.activate('gt-lg'); - matchMedia.activate('invalid'); - expect(count).toEqual(2); + matchMedia.activate('gt-md'); + matchMedia.activate('gt-lg'); + matchMedia.activate('invalid'); + expect(count).toEqual(2); - subscription.unsubscribe(); - })); + subscription.unsubscribe(); + })); it('can subscribe to built-in mediaQueries', inject( - [ObservableMedia, MatchMedia], - (media$, matchMedia) => { - let current: MediaChange; + [ObservableMedia, MatchMedia], + (media$, matchMedia) => { + let current: MediaChange; - expect(media$).toBeDefined(); + expect(media$).toBeDefined(); - let subscription = media$.subscribe((change: MediaChange) => { - current = change; - }); + let subscription = media$.subscribe((change: MediaChange) => { + current = change; + }); - async(() => { - // Confirm initial match is for 'all' - expect(current).toBeDefined(); - expect(current.matches).toBeTruthy(); - expect(current.mediaQuery).toEqual('all'); + async(() => { + // Confirm initial match is for 'all' + expect(current).toBeDefined(); + expect(current.matches).toBeTruthy(); + expect(current.mediaQuery).toEqual('all'); - try { - matchMedia.autoRegisterQueries = false; + try { + matchMedia.autoRegisterQueries = false; - // Activate mediaQuery associated with 'md' alias - matchMedia.activate('md'); - expect(current.mediaQuery).toEqual(findMediaQuery('md')); + // Activate mediaQuery associated with 'md' alias + matchMedia.activate('md'); + expect(current.mediaQuery).toEqual(findMediaQuery('md')); - // Allow overlapping activations to be announced to observers - media$.filterOverlaps = false; + // Allow overlapping activations to be announced to observers + media$.filterOverlaps = false; - matchMedia.activate('gt-lg'); - expect(current.mediaQuery).toEqual(findMediaQuery('gt-lg')); + matchMedia.activate('gt-lg'); + expect(current.mediaQuery).toEqual(findMediaQuery('gt-lg')); - matchMedia.activate('unknown'); - expect(current.mediaQuery).toEqual(findMediaQuery('gt-lg')); + matchMedia.activate('unknown'); + expect(current.mediaQuery).toEqual(findMediaQuery('gt-lg')); - } finally { - matchMedia.autoRegisterQueries = true; - subscription.unsubscribe(); - } - }); - })); + } finally { + matchMedia.autoRegisterQueries = true; + subscription.unsubscribe(); + } + }); + })); it('can `.unsubscribe()` properly', inject( - [ObservableMedia, MatchMedia], - (media, matchMedia) => { - let current: MediaChange; - let subscription = media.subscribe((change: MediaChange) => { - current = change; - }); - - async(() => { - // Activate mediaQuery associated with 'md' alias - matchMedia.activate('md'); - expect(current.mediaQuery).toEqual(findMediaQuery('md')); + [ObservableMedia, MatchMedia], + (media, matchMedia) => { + let current: MediaChange; + let subscription = media.subscribe((change: MediaChange) => { + current = change; + }); + + async(() => { + // Activate mediaQuery associated with 'md' alias + matchMedia.activate('md'); + expect(current.mediaQuery).toEqual(findMediaQuery('md')); - // Un-subscribe - subscription.unsubscribe(); + // Un-subscribe + subscription.unsubscribe(); - matchMedia.activate('lg'); - expect(current.mqAlias).toBe('md'); + matchMedia.activate('lg'); + expect(current.mqAlias).toBe('md'); - matchMedia.activate('xs'); - expect(current.mqAlias).toBe('md'); - }); - })); + matchMedia.activate('xs'); + expect(current.mqAlias).toBe('md'); + }); + })); it('can observe a startup activation of XS', inject( - [ObservableMedia, MatchMedia], - (media, matchMedia) => { - let current: MediaChange; - let subscription = media.subscribe((change: MediaChange) => { - current = change; - }); - - async(() => { - // Activate mediaQuery associated with 'md' alias - matchMedia.activate('xs'); - expect(current.mediaQuery).toEqual(findMediaQuery('xs')); + [ObservableMedia, MatchMedia], + (media, matchMedia) => { + let current: MediaChange; + let subscription = media.subscribe((change: MediaChange) => { + current = change; + }); + + async(() => { + // Activate mediaQuery associated with 'md' alias + matchMedia.activate('xs'); + expect(current.mediaQuery).toEqual(findMediaQuery('xs')); - // Un-subscribe - subscription.unsubscribe(); + // Un-subscribe + subscription.unsubscribe(); - matchMedia.activate('lg'); - expect(current.mqAlias).toBe('xs'); - }); - })); + matchMedia.activate('lg'); + expect(current.mqAlias).toBe('xs'); + }); + })); }); describe('with custom BreakPoints', () => { - const excludeDefaults = true; const gtXsMediaQuery = 'screen and (max-width:20px) and (orientations: landscape)'; const mdMediaQuery = 'print and (min-width:10000px)'; const CUSTOM_BREAKPOINTS = [ @@ -197,32 +194,32 @@ describe('observable-media', () => { providers: [ BreakPointRegistry, // Registry of known/used BreakPoint(s) MockMatchMediaProvider, - CUSTOM_BREAKPOINTS_PROVIDER_FACTORY(CUSTOM_BREAKPOINTS, {defaults: excludeDefaults}), + BREAKPOINTS_PROVIDER, + {provide: BREAKPOINT, useValue: CUSTOM_BREAKPOINTS, multi: true}, OBSERVABLE_MEDIA_PROVIDER, ] }); }); it('can activate custom alias with custom mediaQueries', inject( - [ObservableMedia, MatchMedia], - (media, matchMedia) => { - let current: MediaChange; - let subscription = media.subscribe((change: MediaChange) => { - current = change; - }); - - async(() => { - // Activate mediaQuery associated with 'md' alias - matchMedia.activate('print.md'); - expect(current.mediaQuery).toEqual(mdMediaQuery); + [ObservableMedia, MatchMedia], + (media, matchMedia) => { + let current: MediaChange; + let subscription = media.subscribe((change: MediaChange) => { + current = change; + }); + + async(() => { + // Activate mediaQuery associated with 'md' alias + matchMedia.activate('print.md'); + expect(current.mediaQuery).toEqual(mdMediaQuery); - matchMedia.activate('tablet-gt-xs'); - expect(current.mqAlias).toBe('tablet-gt-xs'); - expect(current.mediaQuery).toBe(gtXsMediaQuery); + matchMedia.activate('tablet-gt-xs'); + expect(current.mqAlias).toBe('tablet-gt-xs'); + expect(current.mediaQuery).toBe(gtXsMediaQuery); - subscription.unsubscribe(); - }); - })); + subscription.unsubscribe(); + }); + })); }); }); - diff --git a/src/lib/core/public-api.ts b/src/lib/core/public-api.ts index 2108d25af..4915338c8 100644 --- a/src/lib/core/public-api.ts +++ b/src/lib/core/public-api.ts @@ -11,7 +11,7 @@ export * from './module'; export * from './media-queries-module'; export * from './media-change'; export * from './stylesheet-map/index'; -export * from './server-token'; +export * from './tokens/index'; export * from './base/index'; export * from './breakpoints/index'; diff --git a/src/lib/core/responsive-activation/responsive-activation.spec.ts b/src/lib/core/responsive-activation/responsive-activation.spec.ts index 86d6c7c30..dd3e8ff18 100644 --- a/src/lib/core/responsive-activation/responsive-activation.spec.ts +++ b/src/lib/core/responsive-activation/responsive-activation.spec.ts @@ -5,11 +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 {TestBed, inject} from '@angular/core/testing'; +import {TestBed, inject, fakeAsync} from '@angular/core/testing'; -import {DEFAULT_BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; +import {BREAKPOINTS_PROVIDER} from '../breakpoints/break-points-provider'; import {BreakPointRegistry} from '../breakpoints/break-point-registry'; -import {MockMatchMedia} from '../match-media/mock/mock-match-media'; +import {MockMatchMedia, MockMatchMediaProvider} from '../match-media/mock/mock-match-media'; import {MatchMedia} from '../match-media/match-media'; import {MediaMonitor} from '../media-monitor/media-monitor'; import {ResponsiveActivation, KeyOptions} from './responsive-activation'; @@ -38,29 +38,28 @@ describe('responsive-activation', () => { TestBed.configureTestingModule({ providers: [ MediaMonitor, - BreakPointRegistry, // Registry of known/used BreakPoint(s) - DEFAULT_BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints - {provide: MatchMedia, useClass: MockMatchMedia} + BreakPointRegistry, // Registry of known/used BreakPoint(s) + BREAKPOINTS_PROVIDER, // Supports developer overrides of list of known breakpoints + MockMatchMediaProvider, ] }); }); // Single async inject to save references; which are used in all tests below beforeEach(inject( - [MatchMedia, MediaMonitor], - (_matchMedia, _mediaMonitor) => { - matchMedia = _matchMedia; // Only used to manual/simulate activate a mediaQuery - monitor = _mediaMonitor; - } + [MatchMedia, MediaMonitor], + (_matchMedia, _mediaMonitor) => { + matchMedia = _matchMedia; // Only used to manual/simulate activate a mediaQuery + monitor = _mediaMonitor; + } )); it('does not report mediaQuery changes for static usages', () => { let value; let onMediaChange = (changes: MediaChange) => value = changes.value; let responder = buildResponder('layout', 'row', onMediaChange); - try { + fakeAsync(() => { // Confirm static values are returned as expected - expect(value).toBeUndefined(); expect(responder.activatedInputKey).toEqual('layout'); expect(responder.activatedInput).toEqual('row'); @@ -68,7 +67,6 @@ describe('responsive-activation', () => { // No responsive inputs were defined, so any mediaQuery // activations should not affect anything and the change handler // should NOT have been called. - matchMedia.activate('xs'); expect(value).toBeUndefined(); @@ -81,23 +79,22 @@ describe('responsive-activation', () => { expect(responder.activatedInputKey).toEqual('layout'); expect(responder.activatedInput).toEqual('row'); - } finally { responder.destroy(); - } + }); }); it('reports mediaQuery changes for responsive usages', () => { let value; let onMediaChange = (changes: MediaChange) => value = changes.value; let responder = buildResponder('layout', 'row', onMediaChange, { - 'layout': 'row', - 'layoutXs': 'column', // define trigger to 'xs' mediaQuery - 'layoutMd': 'column-reverse', // define trigger to 'md' mediaQuery - 'layoutGtLg': 'row-reverse' // define trigger to 'md' mediaQuery - } + 'layout': 'row', + 'layoutXs': 'column', // define trigger to 'xs' mediaQuery + 'layoutMd': 'column-reverse', // define trigger to 'md' mediaQuery + 'layoutGtLg': 'row-reverse' // define trigger to 'md' mediaQuery + } ); - try { + fakeAsync(() => { expect(value).toBeUndefined(); matchMedia.activate('xs'); @@ -109,21 +106,20 @@ describe('responsive-activation', () => { matchMedia.activate('gt-lg'); expect(value).toEqual('row-reverse'); - } finally { responder.destroy(); - } + }); }); it('uses fallback to default input if the activated mediaQuery should be ignored', () => { let value; let onMediaChange = (changes: MediaChange) => value = changes.value; let responder = buildResponder('layout', 'row', onMediaChange, { - 'layout': 'row', - 'layoutXs': 'column', // define input value link to 'xs' mediaQuery - } + 'layout': 'row', + 'layoutXs': 'column', // define input value link to 'xs' mediaQuery + } ); - try { + fakeAsync(() => { expect(value).toBeUndefined(); matchMedia.activate('xs'); @@ -131,26 +127,24 @@ describe('responsive-activation', () => { // No input 'layoutMd' has been defined, so the fallback // to 'layout' input value should be used... - matchMedia.activate('md'); expect(value).toEqual('row'); - } finally { responder.destroy(); - } + }); }); it('uses closest responsive input value if the activated mediaQuery is not linked', () => { let value, enableOverlaps = false; let onMediaChange = (changes: MediaChange) => value = changes.value; let responder = buildResponder('layout', 'row', onMediaChange, { - 'layout': 'row', - 'layoutXs': 'column', // define link to 'xs' mediaQuery - 'layoutGtSm': 'row-reverse' // define link to 'gt-sm' mediaQuery - } + 'layout': 'row', + 'layoutXs': 'column', // define link to 'xs' mediaQuery + 'layoutGtSm': 'row-reverse' // define link to 'gt-sm' mediaQuery + } ); - try { + fakeAsync(() => { expect(value).toBeUndefined(); matchMedia.activate('xs'); @@ -158,13 +152,11 @@ describe('responsive-activation', () => { // No input 'layoutMd' has been defined, so the fallback // to 'layoutGtSm' input value should be used... - matchMedia.activate('md', enableOverlaps = true); expect(value).toEqual('row-reverse'); - } finally { responder.destroy(); - } + }); }); }); diff --git a/src/lib/core/style-utils/style-utils.ts b/src/lib/core/style-utils/style-utils.ts index f3223294b..160913d09 100644 --- a/src/lib/core/style-utils/style-utils.ts +++ b/src/lib/core/style-utils/style-utils.ts @@ -10,14 +10,16 @@ import {isPlatformBrowser, isPlatformServer} from '@angular/common'; import {applyCssPrefixes} from '../../utils/auto-prefixer'; import {StylesheetMap} from '../stylesheet-map/stylesheet-map'; -import {SERVER_TOKEN} from '../server-token'; +import {SERVER_TOKEN} from '../tokens/server-token'; +import {DISABLE_VENDOR_PREFIXES} from '../tokens/vendor-prefixes-token'; @Injectable() export class StyleUtils { constructor(@Optional() private _serverStylesheet: StylesheetMap, @Optional() @Inject(SERVER_TOKEN) private _serverModuleLoaded: boolean, - @Inject(PLATFORM_ID) private _platformId) {} + @Inject(PLATFORM_ID) private _platformId, + @Optional() @Inject(DISABLE_VENDOR_PREFIXES) private noVendorPrefixes: boolean) {} /** * Applies styles given via string pair or object map to the directive element @@ -28,7 +30,7 @@ export class StyleUtils { styles[style] = value; style = styles; } - styles = applyCssPrefixes(style); + styles = this.noVendorPrefixes ? style : applyCssPrefixes(style); this._applyMultiValueStyleToElement(styles, element); } @@ -36,7 +38,7 @@ export class StyleUtils { * Applies styles given via string pair or object map to the directive's element */ applyStyleToElements(style: StyleDefinition, elements: HTMLElement[] = []) { - const styles = applyCssPrefixes(style); + const styles = this.noVendorPrefixes ? style : applyCssPrefixes(style); elements.forEach(el => { this._applyMultiValueStyleToElement(styles, el); }); diff --git a/src/lib/core/tokens/breakpoint-token.ts b/src/lib/core/tokens/breakpoint-token.ts new file mode 100644 index 000000000..987b59ecb --- /dev/null +++ b/src/lib/core/tokens/breakpoint-token.ts @@ -0,0 +1,18 @@ +/** + * @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 {InjectionToken} from '@angular/core'; +import {BreakPoint} from '../breakpoints/break-point'; + +export const DISABLE_DEFAULT_BREAKPOINTS = new InjectionToken( + 'Flex Layout token, disable the default breakpoints'); + +export const ADD_ORIENTATION_BREAKPOINTS = new InjectionToken( + 'Flex Layout token, add the orientation breakpoints'); + +export const BREAKPOINT = new InjectionToken( + 'Flex Layout token, collect all breakpoints into one provider'); diff --git a/src/lib/core/tokens/flex-styles-token.ts b/src/lib/core/tokens/flex-styles-token.ts new file mode 100644 index 000000000..848c9b700 --- /dev/null +++ b/src/lib/core/tokens/flex-styles-token.ts @@ -0,0 +1,11 @@ +/** + * @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 {InjectionToken} from '@angular/core'; + +export const ADD_FLEX_STYLES = new InjectionToken( + 'Flex Layout token, should flex stylings be applied to parents automatically'); diff --git a/src/lib/core/tokens/index.ts b/src/lib/core/tokens/index.ts new file mode 100644 index 000000000..269373bf6 --- /dev/null +++ b/src/lib/core/tokens/index.ts @@ -0,0 +1,12 @@ +/** + * @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 + */ + +export * from './flex-styles-token'; +export * from './server-token'; +export * from './breakpoint-token'; +export * from './vendor-prefixes-token'; diff --git a/src/lib/core/server-token.ts b/src/lib/core/tokens/server-token.ts similarity index 100% rename from src/lib/core/server-token.ts rename to src/lib/core/tokens/server-token.ts diff --git a/src/lib/core/tokens/vendor-prefixes-token.ts b/src/lib/core/tokens/vendor-prefixes-token.ts new file mode 100644 index 000000000..d03802839 --- /dev/null +++ b/src/lib/core/tokens/vendor-prefixes-token.ts @@ -0,0 +1,11 @@ +/** + * @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 {InjectionToken} from '@angular/core'; + +export const DISABLE_VENDOR_PREFIXES = new InjectionToken( + 'Flex Layout token, whether to add vendor prefix styles inline for elements'); diff --git a/src/lib/extended/class/class.spec.ts b/src/lib/extended/class/class.spec.ts index 20e5f9d8e..e0e515cec 100644 --- a/src/lib/extended/class/class.spec.ts +++ b/src/lib/extended/class/class.spec.ts @@ -10,13 +10,10 @@ import {CommonModule, isPlatformServer} from '@angular/common'; import {ComponentFixture, TestBed, async, inject} from '@angular/core/testing'; import {MatButtonModule} from '@angular/material'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, CoreModule, MockMatchMedia, - StylesheetMap, - StyleUtils + MockMatchMediaProvider, } from '@angular/flex-layout/core'; import {customMatchers, expect} from '../../utils/testing/custom-matchers'; @@ -48,12 +45,7 @@ describe('class directive', () => { CoreModule ], declarations: [TestClassComponent, ClassDirective], - providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StylesheetMap, - StyleUtils, - ] + providers: [MockMatchMediaProvider] }); }); diff --git a/src/lib/extended/img-src/img-src.spec.ts b/src/lib/extended/img-src/img-src.spec.ts index 71449453c..a39d8580d 100644 --- a/src/lib/extended/img-src/img-src.spec.ts +++ b/src/lib/extended/img-src/img-src.spec.ts @@ -9,10 +9,9 @@ import {Component, PLATFORM_ID} from '@angular/core'; import {CommonModule, isPlatformServer} from '@angular/common'; import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, MockMatchMedia, + MockMatchMediaProvider, SERVER_TOKEN, StyleUtils, } from '@angular/flex-layout/core'; @@ -76,8 +75,7 @@ describe('img-src directive', () => { imports: [CommonModule, FlexLayoutModule], declarations: [TestSrcComponent], providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, + MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true} ] }); diff --git a/src/lib/extended/show-hide/hide.spec.ts b/src/lib/extended/show-hide/hide.spec.ts index 810fcec8f..ae4d906d1 100644 --- a/src/lib/extended/show-hide/hide.spec.ts +++ b/src/lib/extended/show-hide/hide.spec.ts @@ -9,13 +9,11 @@ import {Component, OnInit, PLATFORM_ID} from '@angular/core'; import {CommonModule, isPlatformServer} from '@angular/common'; import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, CoreModule, MockMatchMedia, + MockMatchMediaProvider, ObservableMedia, - StylesheetMap, SERVER_TOKEN, StyleUtils, } from '@angular/flex-layout/core'; @@ -67,10 +65,7 @@ describe('hide directive', () => { imports: [CommonModule, CoreModule], declarations: [TestHideComponent, ShowHideDirective], providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StylesheetMap, - StyleUtils, + MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true}, ] }); diff --git a/src/lib/extended/show-hide/show.spec.ts b/src/lib/extended/show-hide/show.spec.ts index 739761926..a78c7128c 100644 --- a/src/lib/extended/show-hide/show.spec.ts +++ b/src/lib/extended/show-hide/show.spec.ts @@ -9,12 +9,10 @@ import {Component, OnInit, PLATFORM_ID} from '@angular/core'; import {CommonModule, isPlatformServer} from '@angular/common'; import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, MockMatchMedia, + MockMatchMediaProvider, ObservableMedia, - StylesheetMap, SERVER_TOKEN, StyleUtils, } from '@angular/flex-layout/core'; @@ -47,10 +45,7 @@ describe('show directive', () => { imports: [CommonModule, FlexLayoutModule], declarations: [TestShowComponent], providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StylesheetMap, - StyleUtils, + MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true} ] }); diff --git a/src/lib/extended/style/style.spec.ts b/src/lib/extended/style/style.spec.ts index e732e01ea..8ecff1589 100644 --- a/src/lib/extended/style/style.spec.ts +++ b/src/lib/extended/style/style.spec.ts @@ -9,12 +9,10 @@ import {Component} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, CoreModule, MockMatchMedia, - StylesheetMap, + MockMatchMediaProvider, StyleUtils, } from '@angular/flex-layout/core'; import {LayoutDirective} from '@angular/flex-layout/flex'; @@ -45,12 +43,7 @@ describe('style directive', () => { TestBed.configureTestingModule({ imports: [CommonModule, CoreModule], declarations: [TestStyleComponent, LayoutDirective, StyleDirective], - providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StylesheetMap, - StyleUtils, - ] + providers: [MockMatchMediaProvider] }); }); diff --git a/src/lib/flex/flex-offset/flex-offset.spec.ts b/src/lib/flex/flex-offset/flex-offset.spec.ts index 47bfb9841..262b6e7cf 100644 --- a/src/lib/flex/flex-offset/flex-offset.spec.ts +++ b/src/lib/flex/flex-offset/flex-offset.spec.ts @@ -10,14 +10,7 @@ import {CommonModule, isPlatformServer} from '@angular/common'; import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; import {Platform, PlatformModule} from '@angular/cdk/platform'; import {DIR_DOCUMENT} from '@angular/cdk/bidi'; -import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, - MatchMedia, - MockMatchMedia, - SERVER_TOKEN, - StyleUtils, -} from '@angular/flex-layout/core'; +import {SERVER_TOKEN, StyleUtils} from '@angular/flex-layout/core'; import {FlexLayoutModule} from '../../module'; import {customMatchers} from '../../utils/testing/custom-matchers'; @@ -54,10 +47,7 @@ describe('flex-offset directive', () => { imports: [CommonModule, FlexLayoutModule, PlatformModule], declarations: [TestFlexComponent], providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, {provide: DIR_DOCUMENT, useValue: fakeDocument}, - StyleUtils, {provide: SERVER_TOKEN, useValue: true} ] }); diff --git a/src/lib/flex/flex/flex.spec.ts b/src/lib/flex/flex/flex.spec.ts index d7af773a7..0595bf238 100644 --- a/src/lib/flex/flex/flex.spec.ts +++ b/src/lib/flex/flex/flex.spec.ts @@ -10,10 +10,11 @@ import {CommonModule, isPlatformServer} from '@angular/common'; import {ComponentFixture, TestBed, async, inject} from '@angular/core/testing'; import {Platform, PlatformModule} from '@angular/cdk/platform'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, + ADD_FLEX_STYLES, + DISABLE_VENDOR_PREFIXES, MatchMedia, MockMatchMedia, + MockMatchMediaProvider, SERVER_TOKEN, StyleUtils, } from '@angular/flex-layout/core'; @@ -57,10 +58,7 @@ describe('flex directive', () => { imports: [CommonModule, FlexLayoutModule, PlatformModule], declarations: [TestFlexComponent, TestQueryWithFlexComponent], providers: [ - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StyleUtils, + MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true} ] }); @@ -251,7 +249,7 @@ describe('flex directive', () => { // TODO(CaerusKaru): Domino is unable to detect this style if (!isPlatformServer(platformId)) { // parent flex-direction found with 'column' with child height styles - expectEl(parent).toHaveStyle({'flex-direction': 'column', 'display': 'flex'}, styler); + expectEl(parent).toHaveCSS({'flex-direction': 'column', 'display': 'flex'}, styler); expectEl(element).toHaveStyle({'min-height': '30px'}, styler); expectEl(element).not.toHaveStyle({'min-width': '30px'}, styler); } @@ -273,7 +271,7 @@ describe('flex directive', () => { // The parent flex-direction not found; // A flex-direction should have been auto-injected to the parent... // fallback to 'row' and set child width styles accordingly - expectEl(parent).toHaveStyle({'flex-direction': 'row'}, styler); + expectEl(parent).not.toHaveStyle({'flex-direction': 'row'}, styler); expectEl(element).toHaveStyle({'min-width': '40px'}, styler); expectEl(element).not.toHaveStyle({'min-height': '40px'}, styler); }); @@ -724,6 +722,86 @@ describe('flex directive', () => { ); }); + describe('with flex token enabled', () => { + beforeEach(() => { + jasmine.addMatchers(customMatchers); + + // Configure testbed to prepare services + TestBed.configureTestingModule({ + imports: [CommonModule, FlexLayoutModule, PlatformModule], + declarations: [TestFlexComponent, TestQueryWithFlexComponent], + providers: [ + MockMatchMediaProvider, + {provide: SERVER_TOKEN, useValue: true}, + {provide: ADD_FLEX_STYLES, useValue: true}, + ] + }); + }); + + it('should work with non-direct-parent fxLayouts', async(() => { + componentWithTemplate(` +
+
+
+
+
+ `); + fixture.detectChanges(); + let element = queryFor(fixture, '[fxFlex]')[0]; + let parent = queryFor(fixture, '.test')[0]; + + setTimeout(() => { + // The parent flex-direction not found; + // A flex-direction should have been auto-injected to the parent... + // fallback to 'row' and set child width styles accordingly + expectEl(parent).toHaveStyle({'flex-direction': 'row'}, styler); + expectEl(element).toHaveStyle({'min-width': '40px'}, styler); + expectEl(element).not.toHaveStyle({'min-height': '40px'}, styler); + }); + + })); + }); + + describe('with prefixes disabled', () => { + beforeEach(() => { + jasmine.addMatchers(customMatchers); + + // Configure testbed to prepare services + TestBed.configureTestingModule({ + imports: [CommonModule, FlexLayoutModule, PlatformModule], + declarations: [TestFlexComponent, TestQueryWithFlexComponent], + providers: [ + MockMatchMediaProvider, + {provide: SERVER_TOKEN, useValue: true}, + {provide: DISABLE_VENDOR_PREFIXES, useValue: true}, + ] + }); + }); + + it('should work with non-direct-parent fxLayouts', async(() => { + componentWithTemplate(` +
+
+
+
+
+ `); + fixture.detectChanges(); + let element = queryFor(fixture, '[fxFlex]')[0]; + let parent = queryFor(fixture, '.test')[0]; + + setTimeout(() => { + // The parent flex-direction not found; + // A flex-direction should have been auto-injected to the parent... + // fallback to 'row' and set child width styles accordingly + expectEl(parent).not.toHaveStyle({'-webkit-flex-direction': 'row'}, styler); + expectEl(element).toHaveStyle({'min-width': '40px'}, styler); + expectEl(element).not.toHaveStyle({'min-height': '40px'}, styler); + }); + + })); + }); + }); diff --git a/src/lib/flex/flex/flex.ts b/src/lib/flex/flex/flex.ts index 1e971fbdd..92e0e8310 100644 --- a/src/lib/flex/flex/flex.ts +++ b/src/lib/flex/flex/flex.ts @@ -8,6 +8,7 @@ import { Directive, ElementRef, + Inject, Input, OnChanges, OnDestroy, @@ -16,7 +17,13 @@ import { SimpleChanges, SkipSelf, } from '@angular/core'; -import {BaseFxDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core'; +import { + ADD_FLEX_STYLES, + BaseFxDirective, + MediaChange, + MediaMonitor, + StyleUtils, +} from '@angular/flex-layout/core'; import {Subscription} from 'rxjs/Subscription'; import {extendObject} from '../../utils/object-extend'; @@ -80,7 +87,8 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, constructor(monitor: MediaMonitor, elRef: ElementRef, @Optional() @SkipSelf() protected _container: LayoutDirective, - protected styleUtils: StyleUtils) { + protected styleUtils: StyleUtils, + @Optional() @Inject(ADD_FLEX_STYLES) protected addFlexStyles: boolean|null) { super(monitor, elRef, styleUtils); this._cacheInput('flex', ''); @@ -155,7 +163,7 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, shrink: number|string, basis: string|number|FlexBasisAlias) { // The flex-direction of this element's flex container. Defaults to 'row'. - let layout = this._getFlowDirection(this.parentElement, true); + let layout = this._getFlowDirection(this.parentElement, !!this.addFlexStyles); let direction = (layout.indexOf('column') > -1) ? 'column' : 'row'; let max = isFlowHorizontal(direction) ? 'max-width' : 'max-height'; diff --git a/src/lib/flex/layout-align/layout-align.spec.ts b/src/lib/flex/layout-align/layout-align.spec.ts index d5c856bb5..2893d0db5 100644 --- a/src/lib/flex/layout-align/layout-align.spec.ts +++ b/src/lib/flex/layout-align/layout-align.spec.ts @@ -10,10 +10,9 @@ import {CommonModule} from '@angular/common'; import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; import {Platform, PlatformModule} from '@angular/cdk/platform'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, MockMatchMedia, + MockMatchMediaProvider, SERVER_TOKEN, StyleUtils, } from '@angular/flex-layout/core'; @@ -47,10 +46,7 @@ describe('layout-align directive', () => { imports: [CommonModule, FlexLayoutModule, PlatformModule], declarations: [TestLayoutAlignComponent], providers: [ - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StyleUtils, + MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true} ] }); diff --git a/src/lib/flex/layout-gap/layout-gap.spec.ts b/src/lib/flex/layout-gap/layout-gap.spec.ts index d7c41236f..84e1c26b4 100644 --- a/src/lib/flex/layout-gap/layout-gap.spec.ts +++ b/src/lib/flex/layout-gap/layout-gap.spec.ts @@ -9,14 +9,7 @@ import {Component, OnInit, PLATFORM_ID} from '@angular/core'; import {CommonModule, isPlatformServer} from '@angular/common'; import {TestBed, ComponentFixture, async, inject} from '@angular/core/testing'; import {DIR_DOCUMENT} from '@angular/cdk/bidi'; -import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, - MatchMedia, - MockMatchMedia, - SERVER_TOKEN, - StyleUtils, -} from '@angular/flex-layout/core'; +import {SERVER_TOKEN, StyleUtils} from '@angular/flex-layout/core'; import {FlexLayoutModule} from '../../module'; import {customMatchers, expect} from '../../utils/testing/custom-matchers'; @@ -45,10 +38,6 @@ describe('layout-gap directive', () => { declarations: [TestLayoutGapComponent], providers: [ {provide: DIR_DOCUMENT, useValue: fakeDocument}, - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StyleUtils, {provide: SERVER_TOKEN, useValue: true} ] }); diff --git a/src/lib/flex/layout/layout.spec.ts b/src/lib/flex/layout/layout.spec.ts index 283447540..f2df55a28 100644 --- a/src/lib/flex/layout/layout.spec.ts +++ b/src/lib/flex/layout/layout.spec.ts @@ -9,10 +9,9 @@ import {Component, OnInit} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; import { - BreakPointRegistry, - DEFAULT_BREAKPOINTS_PROVIDER, MatchMedia, MockMatchMedia, + MockMatchMediaProvider, SERVER_TOKEN, StyleUtils, } from '@angular/flex-layout/core'; @@ -43,9 +42,7 @@ describe('layout directive', () => { imports: [CommonModule, FlexLayoutModule], declarations: [TestLayoutComponent], providers: [ - BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia}, - StyleUtils, + MockMatchMediaProvider, {provide: SERVER_TOKEN, useValue: true} ] });