Skip to content

Commit

Permalink
fix(core): align breakpoints with those used in CDK (#1006)
Browse files Browse the repository at this point in the history
@angular/cdk BreakpointObserver will not replace `MediaObserver`. **MediaObserver** is an enhanced version that notifies subscribers of activations for standard AND **overlapping (lt-xxx, gt-xxx)**  breakpoints.

* Ensure standard breakpoints mediaQueries are aligned with those in the CDK
* Update MediaObserver
  * isActive() enhanced to support list of aliases to determine if any match
  * properly disconnects subscribers when destroyed

> Note: Developers should use MediaObserver (not use MatchMedia) service to observe breakpoint activations! MediaObserver is the recommended service to use for application developers; MatchMedia should be considered a private service.

Fixes #685. Refs #1001.
  • Loading branch information
ThomasBurleson authored and CaerusKaru committed Jan 23, 2019
1 parent 1154fae commit 6f43cf6
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 40 deletions.
18 changes: 9 additions & 9 deletions src/lib/core/breakpoints/data/break-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,52 @@ import {BreakPoint} from '../break-point';
export const DEFAULT_BREAKPOINTS: BreakPoint[] = [
{
alias: 'xs',
mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.9999px)',
mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.99px)',
priority: 1000,
},
{
alias: 'sm',
mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.9999px)',
mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.99px)',
priority: 900,
},
{
alias: 'md',
mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.9999px)',
mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.99px)',
priority: 800,
},
{
alias: 'lg',
mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.9999px)',
mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.99px)',
priority: 700,
},
{
alias: 'xl',
mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.9999px)',
mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.99px)',
priority: 600,
},
{
alias: 'lt-sm',
overlapping: true,
mediaQuery: 'screen and (max-width: 599.9999px)',
mediaQuery: 'screen and (max-width: 599.99px)',
priority: 950,
},
{
alias: 'lt-md',
overlapping: true,
mediaQuery: 'screen and (max-width: 959.9999px)',
mediaQuery: 'screen and (max-width: 959.99px)',
priority: 850,
},
{
alias: 'lt-lg',
overlapping: true,
mediaQuery: 'screen and (max-width: 1279.9999px)',
mediaQuery: 'screen and (max-width: 1279.99px)',
priority: 750,
},
{
alias: 'lt-xl',
overlapping: true,
priority: 650,
mediaQuery: 'screen and (max-width: 1919.9999px)',
mediaQuery: 'screen and (max-width: 1919.99px)',
},
{
alias: 'gt-xs',
Expand Down
8 changes: 4 additions & 4 deletions src/lib/core/breakpoints/data/orientation-break-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
import {BreakPoint} from '../break-point';

/* tslint:disable */
const HANDSET_PORTRAIT = '(orientation: portrait) and (max-width: 599px)';
const HANDSET_LANDSCAPE = '(orientation: landscape) and (max-width: 959px)';
const HANDSET_PORTRAIT = '(orientation: portrait) and (max-width: 599.99px)';
const HANDSET_LANDSCAPE = '(orientation: landscape) and (max-width: 959.99px)';

const TABLET_LANDSCAPE = '(orientation: landscape) and (min-width: 960px) and (max-width: 1279px)';
const TABLET_PORTRAIT = '(orientation: portrait) and (min-width: 600px) and (max-width: 839px)';
const TABLET_PORTRAIT = '(orientation: portrait) and (min-width: 600px) and (max-width: 839.99px)';
const TABLET_LANDSCAPE = '(orientation: landscape) and (min-width: 960px) and (max-width: 1279.99px)';

const WEB_PORTRAIT = '(orientation: portrait) and (min-width: 840px)';
const WEB_LANDSCAPE = '(orientation: landscape) and (min-width: 1280px)';
Expand Down
6 changes: 3 additions & 3 deletions src/lib/core/media-observer/media-observer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe('media-observer', () => {
describe('with custom BreakPoints', () => {
const gtXsMediaQuery = 'screen and (min-width:120px) and (orientation:landscape)';
const superXLQuery = 'screen and (min-width:10000px)';
const smMediaQuery = 'screen and (min-width: 600px) and (max-width: 959.9999px)';
const smMediaQuery = 'screen and (min-width: 600px) and (max-width: 959.99px)';

const CUSTOM_BREAKPOINTS = [
{alias: 'slate.xl', priority: 11000, mediaQuery: superXLQuery},
Expand Down Expand Up @@ -236,7 +236,7 @@ describe('media-observer', () => {
});

describe('with layout "print" configured', () => {
const mdMediaQuery = 'screen and (min-width: 960px) and (max-width: 1279.9999px)';
const mdMediaQuery = 'screen and (min-width: 960px) and (max-width: 1279.99px)';

beforeEach(() => {
// Configure testbed to prepare services
Expand Down Expand Up @@ -288,7 +288,7 @@ describe('media-observer', () => {
});

describe('with layout print NOT configured', () => {
const smMediaQuery = 'screen and (min-width: 600px) and (max-width: 959.9999px)';
const smMediaQuery = 'screen and (min-width: 600px) and (max-width: 959.99px)';

beforeEach(() => {
// Configure testbed to prepare services
Expand Down
53 changes: 38 additions & 15 deletions src/lib/core/media-observer/media-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
* 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 {Observable, of} from 'rxjs';
import {debounceTime, filter, map, switchMap} from 'rxjs/operators';
import {Injectable, OnDestroy} from '@angular/core';
import {Subject, asapScheduler, Observable, of} from 'rxjs';
import {debounceTime, filter, map, switchMap, takeUntil} from 'rxjs/operators';

import {mergeAlias} from '../add-alias';
import {MediaChange} from '../media-change';
import {MatchMedia} from '../match-media/match-media';
import {PrintHook} from '../media-marshaller/print-hook';
import {BreakPointRegistry, OptionalBreakPoint} from '../breakpoints/break-point-registry';

import {sortDescendingPriority} from '../utils/sort';
import {coerceArray} from '../utils/array';


/**
* MediaObserver enables applications to listen for 1..n mediaQuery activations and to determine
Expand Down Expand Up @@ -58,7 +61,7 @@ import {sortDescendingPriority} from '../utils/sort';
* }
*/
@Injectable({providedIn: 'root'})
export class MediaObserver {
export class MediaObserver implements OnDestroy {

/**
* @deprecated Use `asObservable()` instead.
Expand All @@ -80,6 +83,14 @@ export class MediaObserver {
);
}

/**
* Completes the active subject, signalling to all complete for all
* MediaObserver subscribers
*/
ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}

// ************************************************
// Public Methods
Expand All @@ -93,18 +104,19 @@ export class MediaObserver {
}

/**
* Allow programmatic query to determine if specified query/alias is active.
* Allow programmatic query to determine if one or more media query/alias match
* the current viewport size.
* @param value One or more media queries (or aliases) to check.
* @returns Whether any of the media queries match.
*/
isActive(alias: string): boolean {
const query = toMediaQuery(alias, this.breakpoints);
return this.matchMedia.isActive(query);
isActive(value: string | string[]): boolean {
const aliases = splitQueries(coerceArray(value));
return aliases.some(alias => {
const query = toMediaQuery(alias, this.breakpoints);
return this.matchMedia.isActive(query);
});
}

/**
* Subscribers to activation list can use this function to easily exclude overlaps
*/


// ************************************************
// Internal Methods
// ************************************************
Expand Down Expand Up @@ -151,10 +163,11 @@ export class MediaObserver {
.observe(this.hook.withPrintQuery(mqList))
.pipe(
filter((change: MediaChange) => change.matches),
debounceTime(10),
debounceTime(0, asapScheduler),
switchMap(_ => of(this.findAllActivations())),
map(excludeOverlaps),
filter(hasChanges)
filter(hasChanges),
takeUntil(this.destroyed$)
);
}

Expand All @@ -180,6 +193,7 @@ export class MediaObserver {
}

private readonly _media$: Observable<MediaChange[]>;
private readonly destroyed$ = new Subject<void>();
}

/**
Expand All @@ -190,3 +204,12 @@ function toMediaQuery(query: string, locator: BreakPointRegistry) {
return bp ? bp.mediaQuery : query;
}

/**
* Split each query string into separate query strings if two queries are provided as comma
* separated.
*/
function splitQueries(queries: string[]): string[] {
return queries.map((query: string) => query.split(','))
.reduce((a1: string[], a2: string[]) => a1.concat(a2))
.map(query => query.trim());
}
18 changes: 9 additions & 9 deletions src/lib/core/sass/_layout-bp.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
$breakpoints: (
xs: (
begin: 0,
end: 599.9999px
end: 599.99px
),
sm: (
begin: 600px,
end: 959.9999px
end: 959.99px
),
md: (
begin: 960px,
end: 1279.9999px
end: 1279.99px
),
lg: (
begin: 1280px,
end: 1919.9999px
end: 1919.99px
),
xl: (
begin: 1920px,
end: 4999.9999px
end: 4999.99px
),
) !default;

Expand All @@ -39,10 +39,10 @@ $overlapping-gt: (
// Material Design breakpoints
// @type map
$overlapping-lt: (
lt-sm: 599.9999px,
lt-md: 959.9999px,
lt-lg: 1279.9999px,
lt-xl: 1919.9999px,
lt-sm: 599.99px,
lt-md: 959.99px,
lt-lg: 1279.99px,
lt-xl: 1919.99px,
) !default;


Expand Down
12 changes: 12 additions & 0 deletions src/lib/core/utils/array.ts
Original file line number Diff line number Diff line change
@@ -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
*/

/** Wraps the provided value in an array, unless the provided value is an array. */
export function coerceArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}
1 change: 1 addition & 0 deletions src/lib/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
export * from './sort';
export * from './array';

0 comments on commit 6f43cf6

Please sign in to comment.