Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 6f43cf6

Browse files
ThomasBurlesonCaerusKaru
authored andcommitted
fix(core): align breakpoints with those used in CDK (#1006)
@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.
1 parent 1154fae commit 6f43cf6

File tree

7 files changed

+76
-40
lines changed

7 files changed

+76
-40
lines changed

src/lib/core/breakpoints/data/break-points.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,52 +13,52 @@ import {BreakPoint} from '../break-point';
1313
export const DEFAULT_BREAKPOINTS: BreakPoint[] = [
1414
{
1515
alias: 'xs',
16-
mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.9999px)',
16+
mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.99px)',
1717
priority: 1000,
1818
},
1919
{
2020
alias: 'sm',
21-
mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.9999px)',
21+
mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.99px)',
2222
priority: 900,
2323
},
2424
{
2525
alias: 'md',
26-
mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.9999px)',
26+
mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.99px)',
2727
priority: 800,
2828
},
2929
{
3030
alias: 'lg',
31-
mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.9999px)',
31+
mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.99px)',
3232
priority: 700,
3333
},
3434
{
3535
alias: 'xl',
36-
mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.9999px)',
36+
mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.99px)',
3737
priority: 600,
3838
},
3939
{
4040
alias: 'lt-sm',
4141
overlapping: true,
42-
mediaQuery: 'screen and (max-width: 599.9999px)',
42+
mediaQuery: 'screen and (max-width: 599.99px)',
4343
priority: 950,
4444
},
4545
{
4646
alias: 'lt-md',
4747
overlapping: true,
48-
mediaQuery: 'screen and (max-width: 959.9999px)',
48+
mediaQuery: 'screen and (max-width: 959.99px)',
4949
priority: 850,
5050
},
5151
{
5252
alias: 'lt-lg',
5353
overlapping: true,
54-
mediaQuery: 'screen and (max-width: 1279.9999px)',
54+
mediaQuery: 'screen and (max-width: 1279.99px)',
5555
priority: 750,
5656
},
5757
{
5858
alias: 'lt-xl',
5959
overlapping: true,
6060
priority: 650,
61-
mediaQuery: 'screen and (max-width: 1919.9999px)',
61+
mediaQuery: 'screen and (max-width: 1919.99px)',
6262
},
6363
{
6464
alias: 'gt-xs',

src/lib/core/breakpoints/data/orientation-break-points.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
import {BreakPoint} from '../break-point';
1010

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

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

1818
const WEB_PORTRAIT = '(orientation: portrait) and (min-width: 840px)';
1919
const WEB_LANDSCAPE = '(orientation: landscape) and (min-width: 1280px)';

src/lib/core/media-observer/media-observer.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ describe('media-observer', () => {
181181
describe('with custom BreakPoints', () => {
182182
const gtXsMediaQuery = 'screen and (min-width:120px) and (orientation:landscape)';
183183
const superXLQuery = 'screen and (min-width:10000px)';
184-
const smMediaQuery = 'screen and (min-width: 600px) and (max-width: 959.9999px)';
184+
const smMediaQuery = 'screen and (min-width: 600px) and (max-width: 959.99px)';
185185

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

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

241241
beforeEach(() => {
242242
// Configure testbed to prepare services
@@ -288,7 +288,7 @@ describe('media-observer', () => {
288288
});
289289

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

293293
beforeEach(() => {
294294
// Configure testbed to prepare services

src/lib/core/media-observer/media-observer.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {Injectable} from '@angular/core';
9-
import {Observable, of} from 'rxjs';
10-
import {debounceTime, filter, map, switchMap} from 'rxjs/operators';
8+
import {Injectable, OnDestroy} from '@angular/core';
9+
import {Subject, asapScheduler, Observable, of} from 'rxjs';
10+
import {debounceTime, filter, map, switchMap, takeUntil} from 'rxjs/operators';
1111

1212
import {mergeAlias} from '../add-alias';
1313
import {MediaChange} from '../media-change';
1414
import {MatchMedia} from '../match-media/match-media';
1515
import {PrintHook} from '../media-marshaller/print-hook';
1616
import {BreakPointRegistry, OptionalBreakPoint} from '../breakpoints/break-point-registry';
17+
1718
import {sortDescendingPriority} from '../utils/sort';
19+
import {coerceArray} from '../utils/array';
20+
1821

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

6366
/**
6467
* @deprecated Use `asObservable()` instead.
@@ -80,6 +83,14 @@ export class MediaObserver {
8083
);
8184
}
8285

86+
/**
87+
* Completes the active subject, signalling to all complete for all
88+
* MediaObserver subscribers
89+
*/
90+
ngOnDestroy(): void {
91+
this.destroyed$.next();
92+
this.destroyed$.complete();
93+
}
8394

8495
// ************************************************
8596
// Public Methods
@@ -93,18 +104,19 @@ export class MediaObserver {
93104
}
94105

95106
/**
96-
* Allow programmatic query to determine if specified query/alias is active.
107+
* Allow programmatic query to determine if one or more media query/alias match
108+
* the current viewport size.
109+
* @param value One or more media queries (or aliases) to check.
110+
* @returns Whether any of the media queries match.
97111
*/
98-
isActive(alias: string): boolean {
99-
const query = toMediaQuery(alias, this.breakpoints);
100-
return this.matchMedia.isActive(query);
112+
isActive(value: string | string[]): boolean {
113+
const aliases = splitQueries(coerceArray(value));
114+
return aliases.some(alias => {
115+
const query = toMediaQuery(alias, this.breakpoints);
116+
return this.matchMedia.isActive(query);
117+
});
101118
}
102119

103-
/**
104-
* Subscribers to activation list can use this function to easily exclude overlaps
105-
*/
106-
107-
108120
// ************************************************
109121
// Internal Methods
110122
// ************************************************
@@ -151,10 +163,11 @@ export class MediaObserver {
151163
.observe(this.hook.withPrintQuery(mqList))
152164
.pipe(
153165
filter((change: MediaChange) => change.matches),
154-
debounceTime(10),
166+
debounceTime(0, asapScheduler),
155167
switchMap(_ => of(this.findAllActivations())),
156168
map(excludeOverlaps),
157-
filter(hasChanges)
169+
filter(hasChanges),
170+
takeUntil(this.destroyed$)
158171
);
159172
}
160173

@@ -180,6 +193,7 @@ export class MediaObserver {
180193
}
181194

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

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

207+
/**
208+
* Split each query string into separate query strings if two queries are provided as comma
209+
* separated.
210+
*/
211+
function splitQueries(queries: string[]): string[] {
212+
return queries.map((query: string) => query.split(','))
213+
.reduce((a1: string[], a2: string[]) => a1.concat(a2))
214+
.map(query => query.trim());
215+
}

src/lib/core/sass/_layout-bp.scss

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@
55
$breakpoints: (
66
xs: (
77
begin: 0,
8-
end: 599.9999px
8+
end: 599.99px
99
),
1010
sm: (
1111
begin: 600px,
12-
end: 959.9999px
12+
end: 959.99px
1313
),
1414
md: (
1515
begin: 960px,
16-
end: 1279.9999px
16+
end: 1279.99px
1717
),
1818
lg: (
1919
begin: 1280px,
20-
end: 1919.9999px
20+
end: 1919.99px
2121
),
2222
xl: (
2323
begin: 1920px,
24-
end: 4999.9999px
24+
end: 4999.99px
2525
),
2626
) !default;
2727

@@ -39,10 +39,10 @@ $overlapping-gt: (
3939
// Material Design breakpoints
4040
// @type map
4141
$overlapping-lt: (
42-
lt-sm: 599.9999px,
43-
lt-md: 959.9999px,
44-
lt-lg: 1279.9999px,
45-
lt-xl: 1919.9999px,
42+
lt-sm: 599.99px,
43+
lt-md: 959.99px,
44+
lt-lg: 1279.99px,
45+
lt-xl: 1919.99px,
4646
) !default;
4747

4848

src/lib/core/utils/array.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/** Wraps the provided value in an array, unless the provided value is an array. */
10+
export function coerceArray<T>(value: T | T[]): T[] {
11+
return Array.isArray(value) ? value : [value];
12+
}

src/lib/core/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
export * from './sort';
9+
export * from './array';

0 commit comments

Comments
 (0)