UNPKG

86.8 kBJavaScriptView Raw
1import * as i0 from '@angular/core';
2import { APP_BOOTSTRAP_LISTENER, PLATFORM_ID, NgModule, Injectable, InjectionToken, Inject, inject, Directive } from '@angular/core';
3import { isPlatformBrowser, DOCUMENT, isPlatformServer } from '@angular/common';
4import { BehaviorSubject, Observable, merge, Subject, asapScheduler, of, fromEvent } from 'rxjs';
5import { applyCssPrefixes, extendObject, buildLayoutCSS } from '@angular/flex-layout/_private-utils';
6import { filter, tap, debounceTime, switchMap, map, distinctUntilChanged, takeUntil, take } from 'rxjs/operators';
7
8/**
9 * @license
10 * Copyright Google LLC All Rights Reserved.
11 *
12 * Use of this source code is governed by an MIT-style license that can be
13 * found in the LICENSE file at https://angular.io/license
14 */
15/**
16 * Find all of the server-generated stylings, if any, and remove them
17 * This will be in the form of inline classes and the style block in the
18 * head of the DOM
19 */
20function removeStyles(_document, platformId) {
21 return () => {
22 if (isPlatformBrowser(platformId)) {
23 const elements = Array.from(_document.querySelectorAll(`[class*=${CLASS_NAME}]`));
24 // RegExp constructor should only be used if passing a variable to the constructor.
25 // When using static regular expression it is more performant to use reg exp literal.
26 // This is also needed to provide Safari 9 compatibility, please see
27 // https://stackoverflow.com/questions/37919802 for more discussion.
28 const classRegex = /\bflex-layout-.+?\b/g;
29 elements.forEach(el => {
30 el.classList.contains(`${CLASS_NAME}ssr`) && el.parentNode ?
31 el.parentNode.removeChild(el) : el.className.replace(classRegex, '');
32 });
33 }
34 };
35}
36/**
37 * Provider to remove SSR styles on the browser
38 */
39const BROWSER_PROVIDER = {
40 provide: APP_BOOTSTRAP_LISTENER,
41 useFactory: removeStyles,
42 deps: [DOCUMENT, PLATFORM_ID],
43 multi: true
44};
45const CLASS_NAME = 'flex-layout-';
46
47/**
48 * @license
49 * Copyright Google LLC All Rights Reserved.
50 *
51 * Use of this source code is governed by an MIT-style license that can be
52 * found in the LICENSE file at https://angular.io/license
53 */
54/**
55 * *****************************************************************
56 * Define module for common Angular Layout utilities
57 * *****************************************************************
58 */
59class CoreModule {
60}
61CoreModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: CoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
62CoreModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.1", ngImport: i0, type: CoreModule });
63CoreModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: CoreModule, providers: [BROWSER_PROVIDER] });
64i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: CoreModule, decorators: [{
65 type: NgModule,
66 args: [{
67 providers: [BROWSER_PROVIDER]
68 }]
69 }] });
70
71/**
72 * Class instances emitted [to observers] for each mql notification
73 */
74class MediaChange {
75 /**
76 * @param matches whether the mediaQuery is currently activated
77 * @param mediaQuery e.g. (min-width: 600px) and (max-width: 959px)
78 * @param mqAlias e.g. gt-sm, md, gt-lg
79 * @param suffix e.g. GtSM, Md, GtLg
80 * @param priority the priority of activation for the given breakpoint
81 */
82 constructor(matches = false, mediaQuery = 'all', mqAlias = '', suffix = '', priority = 0) {
83 this.matches = matches;
84 this.mediaQuery = mediaQuery;
85 this.mqAlias = mqAlias;
86 this.suffix = suffix;
87 this.priority = priority;
88 this.property = '';
89 }
90 /** Create an exact copy of the MediaChange */
91 clone() {
92 return new MediaChange(this.matches, this.mediaQuery, this.mqAlias, this.suffix);
93 }
94}
95
96/**
97 * @license
98 * Copyright Google LLC All Rights Reserved.
99 *
100 * Use of this source code is governed by an MIT-style license that can be
101 * found in the LICENSE file at https://angular.io/license
102 */
103/**
104 * Utility to emulate a CSS stylesheet
105 *
106 * This utility class stores all of the styles for a given HTML element
107 * as a readonly `stylesheet` map.
108 */
109class StylesheetMap {
110 constructor() {
111 this.stylesheet = new Map();
112 }
113 /**
114 * Add an individual style to an HTML element
115 */
116 addStyleToElement(element, style, value) {
117 const stylesheet = this.stylesheet.get(element);
118 if (stylesheet) {
119 stylesheet.set(style, value);
120 }
121 else {
122 this.stylesheet.set(element, new Map([[style, value]]));
123 }
124 }
125 /**
126 * Clear the virtual stylesheet
127 */
128 clearStyles() {
129 this.stylesheet.clear();
130 }
131 /**
132 * Retrieve a given style for an HTML element
133 */
134 getStyleForElement(el, styleName) {
135 const styles = this.stylesheet.get(el);
136 let value = '';
137 if (styles) {
138 const style = styles.get(styleName);
139 if (typeof style === 'number' || typeof style === 'string') {
140 value = style + '';
141 }
142 }
143 return value;
144 }
145}
146StylesheetMap.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: StylesheetMap, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
147StylesheetMap.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: StylesheetMap, providedIn: 'root' });
148i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: StylesheetMap, decorators: [{
149 type: Injectable,
150 args: [{ providedIn: 'root' }]
151 }] });
152
153/**
154 * @license
155 * Copyright Google LLC All Rights Reserved.
156 *
157 * Use of this source code is governed by an MIT-style license that can be
158 * found in the LICENSE file at https://angular.io/license
159 */
160
161/**
162 * @license
163 * Copyright Google LLC All Rights Reserved.
164 *
165 * Use of this source code is governed by an MIT-style license that can be
166 * found in the LICENSE file at https://angular.io/license
167 */
168const DEFAULT_CONFIG = {
169 addFlexToParent: true,
170 addOrientationBps: false,
171 disableDefaultBps: false,
172 disableVendorPrefixes: false,
173 serverLoaded: false,
174 useColumnBasisZero: true,
175 printWithBreakpoints: [],
176 mediaTriggerAutoRestore: true,
177 ssrObserveBreakpoints: [],
178 // This is disabled by default because otherwise the multiplier would
179 // run for all users, regardless of whether they're using this feature.
180 // Instead, we disable it by default, which requires this ugly cast.
181 multiplier: undefined,
182 defaultUnit: 'px',
183 detectLayoutDisplay: false,
184};
185const LAYOUT_CONFIG = new InjectionToken('Flex Layout token, config options for the library', {
186 providedIn: 'root',
187 factory: () => DEFAULT_CONFIG
188});
189
190/**
191 * @license
192 * Copyright Google LLC All Rights Reserved.
193 *
194 * Use of this source code is governed by an MIT-style license that can be
195 * found in the LICENSE file at https://angular.io/license
196 */
197/**
198 * Token that is provided to tell whether the FlexLayoutServerModule
199 * has been included in the bundle
200 *
201 * NOTE: This can be manually provided to disable styles when using SSR
202 */
203const SERVER_TOKEN = new InjectionToken('FlexLayoutServerLoaded', {
204 providedIn: 'root',
205 factory: () => false
206});
207
208/**
209 * @license
210 * Copyright Google LLC All Rights Reserved.
211 *
212 * Use of this source code is governed by an MIT-style license that can be
213 * found in the LICENSE file at https://angular.io/license
214 */
215const BREAKPOINT = new InjectionToken('Flex Layout token, collect all breakpoints into one provider', {
216 providedIn: 'root',
217 factory: () => null
218});
219
220/**
221 * @license
222 * Copyright Google LLC All Rights Reserved.
223 *
224 * Use of this source code is governed by an MIT-style license that can be
225 * found in the LICENSE file at https://angular.io/license
226 */
227
228/**
229 * @license
230 * Copyright Google LLC All Rights Reserved.
231 *
232 * Use of this source code is governed by an MIT-style license that can be
233 * found in the LICENSE file at https://angular.io/license
234 */
235/**
236 * For the specified MediaChange, make sure it contains the breakpoint alias
237 * and suffix (if available).
238 */
239function mergeAlias(dest, source) {
240 dest = dest?.clone() ?? new MediaChange();
241 if (source) {
242 dest.mqAlias = source.alias;
243 dest.mediaQuery = source.mediaQuery;
244 dest.suffix = source.suffix;
245 dest.priority = source.priority;
246 }
247 return dest;
248}
249
250/** A class that encapsulates CSS style generation for common directives */
251class StyleBuilder {
252 constructor() {
253 /** Whether to cache the generated output styles */
254 this.shouldCache = true;
255 }
256 /**
257 * Run a side effect computation given the input string and the computed styles
258 * from the build task and the host configuration object
259 * NOTE: This should be a no-op unless an algorithm is provided in a subclass
260 */
261 sideEffect(_input, _styles, _parent) {
262 }
263}
264
265/**
266 * @license
267 * Copyright Google LLC All Rights Reserved.
268 *
269 * Use of this source code is governed by an MIT-style license that can be
270 * found in the LICENSE file at https://angular.io/license
271 */
272class StyleUtils {
273 constructor(_serverStylesheet, _serverModuleLoaded, _platformId, layoutConfig) {
274 this._serverStylesheet = _serverStylesheet;
275 this._serverModuleLoaded = _serverModuleLoaded;
276 this._platformId = _platformId;
277 this.layoutConfig = layoutConfig;
278 }
279 /**
280 * Applies styles given via string pair or object map to the directive element
281 */
282 applyStyleToElement(element, style, value = null) {
283 let styles = {};
284 if (typeof style === 'string') {
285 styles[style] = value;
286 style = styles;
287 }
288 styles = this.layoutConfig.disableVendorPrefixes ? style : applyCssPrefixes(style);
289 this._applyMultiValueStyleToElement(styles, element);
290 }
291 /**
292 * Applies styles given via string pair or object map to the directive's element
293 */
294 applyStyleToElements(style, elements = []) {
295 const styles = this.layoutConfig.disableVendorPrefixes ? style : applyCssPrefixes(style);
296 elements.forEach(el => {
297 this._applyMultiValueStyleToElement(styles, el);
298 });
299 }
300 /**
301 * Determine the DOM element's Flexbox flow (flex-direction)
302 *
303 * Check inline style first then check computed (stylesheet) style
304 */
305 getFlowDirection(target) {
306 const query = 'flex-direction';
307 let value = this.lookupStyle(target, query);
308 const hasInlineValue = this.lookupInlineStyle(target, query) ||
309 (isPlatformServer(this._platformId) && this._serverModuleLoaded) ? value : '';
310 return [value || 'row', hasInlineValue];
311 }
312 hasWrap(target) {
313 const query = 'flex-wrap';
314 return this.lookupStyle(target, query) === 'wrap';
315 }
316 /**
317 * Find the DOM element's raw attribute value (if any)
318 */
319 lookupAttributeValue(element, attribute) {
320 return element.getAttribute(attribute) ?? '';
321 }
322 /**
323 * Find the DOM element's inline style value (if any)
324 */
325 lookupInlineStyle(element, styleName) {
326 return isPlatformBrowser(this._platformId) ?
327 element.style.getPropertyValue(styleName) : getServerStyle(element, styleName);
328 }
329 /**
330 * Determine the inline or inherited CSS style
331 * NOTE: platform-server has no implementation for getComputedStyle
332 */
333 lookupStyle(element, styleName, inlineOnly = false) {
334 let value = '';
335 if (element) {
336 let immediateValue = value = this.lookupInlineStyle(element, styleName);
337 if (!immediateValue) {
338 if (isPlatformBrowser(this._platformId)) {
339 if (!inlineOnly) {
340 value = getComputedStyle(element).getPropertyValue(styleName);
341 }
342 }
343 else {
344 if (this._serverModuleLoaded) {
345 value = this._serverStylesheet.getStyleForElement(element, styleName);
346 }
347 }
348 }
349 }
350 // Note: 'inline' is the default of all elements, unless UA stylesheet overrides;
351 // in which case getComputedStyle() should determine a valid value.
352 return value ? value.trim() : '';
353 }
354 /**
355 * Applies the styles to the element. The styles object map may contain an array of values
356 * Each value will be added as element style
357 * Keys are sorted to add prefixed styles (like -webkit-x) first, before the standard ones
358 */
359 _applyMultiValueStyleToElement(styles, element) {
360 Object.keys(styles).sort().forEach(key => {
361 const el = styles[key];
362 const values = Array.isArray(el) ? el : [el];
363 values.sort();
364 for (let value of values) {
365 value = value ? value + '' : '';
366 if (isPlatformBrowser(this._platformId) || !this._serverModuleLoaded) {
367 isPlatformBrowser(this._platformId) ?
368 element.style.setProperty(key, value) : setServerStyle(element, key, value);
369 }
370 else {
371 this._serverStylesheet.addStyleToElement(element, key, value);
372 }
373 }
374 });
375 }
376}
377StyleUtils.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: StyleUtils, deps: [{ token: StylesheetMap }, { token: SERVER_TOKEN }, { token: PLATFORM_ID }, { token: LAYOUT_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
378StyleUtils.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: StyleUtils, providedIn: 'root' });
379i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: StyleUtils, decorators: [{
380 type: Injectable,
381 args: [{ providedIn: 'root' }]
382 }], ctorParameters: function () { return [{ type: StylesheetMap }, { type: undefined, decorators: [{
383 type: Inject,
384 args: [SERVER_TOKEN]
385 }] }, { type: Object, decorators: [{
386 type: Inject,
387 args: [PLATFORM_ID]
388 }] }, { type: undefined, decorators: [{
389 type: Inject,
390 args: [LAYOUT_CONFIG]
391 }] }]; } });
392function getServerStyle(element, styleName) {
393 const styleMap = readStyleAttribute(element);
394 return styleMap[styleName] ?? '';
395}
396function setServerStyle(element, styleName, styleValue) {
397 styleName = styleName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
398 const styleMap = readStyleAttribute(element);
399 styleMap[styleName] = styleValue ?? '';
400 writeStyleAttribute(element, styleMap);
401}
402function writeStyleAttribute(element, styleMap) {
403 let styleAttrValue = '';
404 for (const key in styleMap) {
405 const newValue = styleMap[key];
406 if (newValue) {
407 styleAttrValue += `${key}:${styleMap[key]};`;
408 }
409 }
410 element.setAttribute('style', styleAttrValue);
411}
412function readStyleAttribute(element) {
413 const styleMap = {};
414 const styleAttribute = element.getAttribute('style');
415 if (styleAttribute) {
416 const styleList = styleAttribute.split(/;+/g);
417 for (let i = 0; i < styleList.length; i++) {
418 const style = styleList[i].trim();
419 if (style.length > 0) {
420 const colonIndex = style.indexOf(':');
421 if (colonIndex === -1) {
422 throw new Error(`Invalid CSS style: ${style}`);
423 }
424 const name = style.substr(0, colonIndex).trim();
425 styleMap[name] = style.substr(colonIndex + 1).trim();
426 }
427 }
428 }
429 return styleMap;
430}
431
432/**
433 * @license
434 * Copyright Google LLC All Rights Reserved.
435 *
436 * Use of this source code is governed by an MIT-style license that can be
437 * found in the LICENSE file at https://angular.io/license
438 */
439/** HOF to sort the breakpoints by descending priority */
440function sortDescendingPriority(a, b) {
441 const priorityA = a ? a.priority || 0 : 0;
442 const priorityB = b ? b.priority || 0 : 0;
443 return priorityB - priorityA;
444}
445/** HOF to sort the breakpoints by ascending priority */
446function sortAscendingPriority(a, b) {
447 const pA = a.priority || 0;
448 const pB = b.priority || 0;
449 return pA - pB;
450}
451
452/**
453 * @license
454 * Copyright Google LLC All Rights Reserved.
455 *
456 * Use of this source code is governed by an MIT-style license that can be
457 * found in the LICENSE file at https://angular.io/license
458 */
459/**
460 * MediaMonitor configures listeners to mediaQuery changes and publishes an Observable facade to
461 * convert mediaQuery change callbacks to subscriber notifications. These notifications will be
462 * performed within the ng Zone to trigger change detections and component updates.
463 *
464 * NOTE: both mediaQuery activations and de-activations are announced in notifications
465 */
466class MatchMedia {
467 constructor(_zone, _platformId, _document) {
468 this._zone = _zone;
469 this._platformId = _platformId;
470 this._document = _document;
471 /** Initialize source with 'all' so all non-responsive APIs trigger style updates */
472 this.source = new BehaviorSubject(new MediaChange(true));
473 this.registry = new Map();
474 this.pendingRemoveListenerFns = [];
475 this._observable$ = this.source.asObservable();
476 }
477 /**
478 * Publish list of all current activations
479 */
480 get activations() {
481 const results = [];
482 this.registry.forEach((mql, key) => {
483 if (mql.matches) {
484 results.push(key);
485 }
486 });
487 return results;
488 }
489 /**
490 * For the specified mediaQuery?
491 */
492 isActive(mediaQuery) {
493 const mql = this.registry.get(mediaQuery);
494 return mql?.matches ?? this.registerQuery(mediaQuery).some(m => m.matches);
495 }
496 /**
497 * External observers can watch for all (or a specific) mql changes.
498 * Typically used by the MediaQueryAdaptor; optionally available to components
499 * who wish to use the MediaMonitor as mediaMonitor$ observable service.
500 *
501 * Use deferred registration process to register breakpoints only on subscription
502 * This logic also enforces logic to register all mediaQueries BEFORE notify
503 * subscribers of notifications.
504 */
505 observe(mqList, filterOthers = false) {
506 if (mqList && mqList.length) {
507 const matchMedia$ = this._observable$.pipe(filter((change) => !filterOthers ? true : (mqList.indexOf(change.mediaQuery) > -1)));
508 const registration$ = new Observable((observer) => {
509 const matches = this.registerQuery(mqList);
510 if (matches.length) {
511 const lastChange = matches.pop();
512 matches.forEach((e) => {
513 observer.next(e);
514 });
515 this.source.next(lastChange); // last match is cached
516 }
517 observer.complete();
518 });
519 return merge(registration$, matchMedia$);
520 }
521 return this._observable$;
522 }
523 /**
524 * Based on the BreakPointRegistry provider, register internal listeners for each unique
525 * mediaQuery. Each listener emits specific MediaChange data to observers
526 */
527 registerQuery(mediaQuery) {
528 const list = Array.isArray(mediaQuery) ? mediaQuery : [mediaQuery];
529 const matches = [];
530 buildQueryCss(list, this._document);
531 list.forEach((query) => {
532 const onMQLEvent = (e) => {
533 this._zone.run(() => this.source.next(new MediaChange(e.matches, query)));
534 };
535 let mql = this.registry.get(query);
536 if (!mql) {
537 mql = this.buildMQL(query);
538 mql.addListener(onMQLEvent);
539 this.pendingRemoveListenerFns.push(() => mql.removeListener(onMQLEvent));
540 this.registry.set(query, mql);
541 }
542 if (mql.matches) {
543 matches.push(new MediaChange(true, query));
544 }
545 });
546 return matches;
547 }
548 ngOnDestroy() {
549 let fn;
550 while (fn = this.pendingRemoveListenerFns.pop()) {
551 fn();
552 }
553 }
554 /**
555 * Call window.matchMedia() to build a MediaQueryList; which
556 * supports 0..n listeners for activation/deactivation
557 */
558 buildMQL(query) {
559 return constructMql(query, isPlatformBrowser(this._platformId));
560 }
561}
562MatchMedia.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatchMedia, deps: [{ token: i0.NgZone }, { token: PLATFORM_ID }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
563MatchMedia.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatchMedia, providedIn: 'root' });
564i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatchMedia, decorators: [{
565 type: Injectable,
566 args: [{ providedIn: 'root' }]
567 }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: Object, decorators: [{
568 type: Inject,
569 args: [PLATFORM_ID]
570 }] }, { type: undefined, decorators: [{
571 type: Inject,
572 args: [DOCUMENT]
573 }] }]; } });
574/**
575 * Private global registry for all dynamically-created, injected style tags
576 * @see prepare(query)
577 */
578const ALL_STYLES = {};
579/**
580 * For Webkit engines that only trigger the MediaQueryList Listener
581 * when there is at least one CSS selector for the respective media query.
582 *
583 * @param mediaQueries
584 * @param _document
585 */
586function buildQueryCss(mediaQueries, _document) {
587 const list = mediaQueries.filter(it => !ALL_STYLES[it]);
588 if (list.length > 0) {
589 const query = list.join(', ');
590 try {
591 const styleEl = _document.createElement('style');
592 styleEl.setAttribute('type', 'text/css');
593 if (!styleEl.styleSheet) {
594 const cssText = `
595/*
596 @angular/flex-layout - workaround for possible browser quirk with mediaQuery listeners
597 see http://bit.ly/2sd4HMP
598*/
599@media ${query} {.fx-query-test{ }}
600`;
601 styleEl.appendChild(_document.createTextNode(cssText));
602 }
603 _document.head.appendChild(styleEl);
604 // Store in private global registry
605 list.forEach(mq => ALL_STYLES[mq] = styleEl);
606 }
607 catch (e) {
608 console.error(e);
609 }
610 }
611}
612function constructMql(query, isBrowser) {
613 const canListen = isBrowser && !!window.matchMedia('all').addListener;
614 return canListen ? window.matchMedia(query) : {
615 matches: query === 'all' || query === '',
616 media: query,
617 addListener: () => {
618 },
619 removeListener: () => {
620 },
621 onchange: null,
622 addEventListener() {
623 },
624 removeEventListener() {
625 },
626 dispatchEvent() {
627 return false;
628 }
629 };
630}
631
632/**
633 * NOTE: Smaller ranges have HIGHER priority since the match is more specific
634 */
635const DEFAULT_BREAKPOINTS = [
636 {
637 alias: 'xs',
638 mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.98px)',
639 priority: 1000,
640 },
641 {
642 alias: 'sm',
643 mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.98px)',
644 priority: 900,
645 },
646 {
647 alias: 'md',
648 mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.98px)',
649 priority: 800,
650 },
651 {
652 alias: 'lg',
653 mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.98px)',
654 priority: 700,
655 },
656 {
657 alias: 'xl',
658 mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.98px)',
659 priority: 600,
660 },
661 {
662 alias: 'lt-sm',
663 overlapping: true,
664 mediaQuery: 'screen and (max-width: 599.98px)',
665 priority: 950,
666 },
667 {
668 alias: 'lt-md',
669 overlapping: true,
670 mediaQuery: 'screen and (max-width: 959.98px)',
671 priority: 850,
672 },
673 {
674 alias: 'lt-lg',
675 overlapping: true,
676 mediaQuery: 'screen and (max-width: 1279.98px)',
677 priority: 750,
678 },
679 {
680 alias: 'lt-xl',
681 overlapping: true,
682 priority: 650,
683 mediaQuery: 'screen and (max-width: 1919.98px)',
684 },
685 {
686 alias: 'gt-xs',
687 overlapping: true,
688 mediaQuery: 'screen and (min-width: 600px)',
689 priority: -950,
690 },
691 {
692 alias: 'gt-sm',
693 overlapping: true,
694 mediaQuery: 'screen and (min-width: 960px)',
695 priority: -850,
696 }, {
697 alias: 'gt-md',
698 overlapping: true,
699 mediaQuery: 'screen and (min-width: 1280px)',
700 priority: -750,
701 },
702 {
703 alias: 'gt-lg',
704 overlapping: true,
705 mediaQuery: 'screen and (min-width: 1920px)',
706 priority: -650,
707 }
708];
709
710/**
711 * @license
712 * Copyright Google LLC All Rights Reserved.
713 *
714 * Use of this source code is governed by an MIT-style license that can be
715 * found in the LICENSE file at https://angular.io/license
716 */
717/* tslint:disable */
718const HANDSET_PORTRAIT = '(orientation: portrait) and (max-width: 599.98px)';
719const HANDSET_LANDSCAPE = '(orientation: landscape) and (max-width: 959.98px)';
720const TABLET_PORTRAIT = '(orientation: portrait) and (min-width: 600px) and (max-width: 839.98px)';
721const TABLET_LANDSCAPE = '(orientation: landscape) and (min-width: 960px) and (max-width: 1279.98px)';
722const WEB_PORTRAIT = '(orientation: portrait) and (min-width: 840px)';
723const WEB_LANDSCAPE = '(orientation: landscape) and (min-width: 1280px)';
724const ScreenTypes = {
725 'HANDSET': `${HANDSET_PORTRAIT}, ${HANDSET_LANDSCAPE}`,
726 'TABLET': `${TABLET_PORTRAIT} , ${TABLET_LANDSCAPE}`,
727 'WEB': `${WEB_PORTRAIT}, ${WEB_LANDSCAPE} `,
728 'HANDSET_PORTRAIT': `${HANDSET_PORTRAIT}`,
729 'TABLET_PORTRAIT': `${TABLET_PORTRAIT} `,
730 'WEB_PORTRAIT': `${WEB_PORTRAIT}`,
731 'HANDSET_LANDSCAPE': `${HANDSET_LANDSCAPE}`,
732 'TABLET_LANDSCAPE': `${TABLET_LANDSCAPE}`,
733 'WEB_LANDSCAPE': `${WEB_LANDSCAPE}`
734};
735/**
736 * Extended Breakpoints for handset/tablets with landscape or portrait orientations
737 */
738const ORIENTATION_BREAKPOINTS = [
739 { 'alias': 'handset', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET },
740 { 'alias': 'handset.landscape', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_LANDSCAPE },
741 { 'alias': 'handset.portrait', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_PORTRAIT },
742 { 'alias': 'tablet', priority: 2100, 'mediaQuery': ScreenTypes.TABLET },
743 { 'alias': 'tablet.landscape', priority: 2100, 'mediaQuery': ScreenTypes.TABLET_LANDSCAPE },
744 { 'alias': 'tablet.portrait', priority: 2100, 'mediaQuery': ScreenTypes.TABLET_PORTRAIT },
745 { 'alias': 'web', priority: 2200, 'mediaQuery': ScreenTypes.WEB, overlapping: true },
746 { 'alias': 'web.landscape', priority: 2200, 'mediaQuery': ScreenTypes.WEB_LANDSCAPE, overlapping: true },
747 { 'alias': 'web.portrait', priority: 2200, 'mediaQuery': ScreenTypes.WEB_PORTRAIT, overlapping: true }
748];
749
750const ALIAS_DELIMITERS = /(\.|-|_)/g;
751function firstUpperCase(part) {
752 let first = part.length > 0 ? part.charAt(0) : '';
753 let remainder = (part.length > 1) ? part.slice(1) : '';
754 return first.toUpperCase() + remainder;
755}
756/**
757 * Converts snake-case to SnakeCase.
758 * @param name Text to UpperCamelCase
759 */
760function camelCase(name) {
761 return name
762 .replace(ALIAS_DELIMITERS, '|')
763 .split('|')
764 .map(firstUpperCase)
765 .join('');
766}
767/**
768 * For each breakpoint, ensure that a Suffix is defined;
769 * fallback to UpperCamelCase the unique Alias value
770 */
771function validateSuffixes(list) {
772 list.forEach((bp) => {
773 if (!bp.suffix) {
774 bp.suffix = camelCase(bp.alias); // create Suffix value based on alias
775 bp.overlapping = !!bp.overlapping; // ensure default value
776 }
777 });
778 return list;
779}
780/**
781 * Merge a custom breakpoint list with the default list based on unique alias values
782 * - Items are added if the alias is not in the default list
783 * - Items are merged with the custom override if the alias exists in the default list
784 */
785function mergeByAlias(defaults, custom = []) {
786 const dict = {};
787 defaults.forEach(bp => {
788 dict[bp.alias] = bp;
789 });
790 // Merge custom breakpoints
791 custom.forEach((bp) => {
792 if (dict[bp.alias]) {
793 extendObject(dict[bp.alias], bp);
794 }
795 else {
796 dict[bp.alias] = bp;
797 }
798 });
799 return validateSuffixes(Object.keys(dict).map(k => dict[k]));
800}
801
802/**
803 * @license
804 * Copyright Google LLC All Rights Reserved.
805 *
806 * Use of this source code is governed by an MIT-style license that can be
807 * found in the LICENSE file at https://angular.io/license
808 */
809/**
810 * Injection token unique to the flex-layout library.
811 * Use this token when build a custom provider (see below).
812 */
813const BREAKPOINTS = new InjectionToken('Token (@angular/flex-layout) Breakpoints', {
814 providedIn: 'root',
815 factory: () => {
816 const breakpoints = inject(BREAKPOINT);
817 const layoutConfig = inject(LAYOUT_CONFIG);
818 const bpFlattenArray = [].concat.apply([], (breakpoints || [])
819 .map((v) => Array.isArray(v) ? v : [v]));
820 const builtIns = (layoutConfig.disableDefaultBps ? [] : DEFAULT_BREAKPOINTS)
821 .concat(layoutConfig.addOrientationBps ? ORIENTATION_BREAKPOINTS : []);
822 return mergeByAlias(builtIns, bpFlattenArray);
823 }
824});
825
826/**
827 * @license
828 * Copyright Google LLC All Rights Reserved.
829 *
830 * Use of this source code is governed by an MIT-style license that can be
831 * found in the LICENSE file at https://angular.io/license
832 */
833/**
834 * Registry of 1..n MediaQuery breakpoint ranges
835 * This is published as a provider and may be overridden from custom, application-specific ranges
836 *
837 */
838class BreakPointRegistry {
839 constructor(list) {
840 /**
841 * Memoized BreakPoint Lookups
842 */
843 this.findByMap = new Map();
844 this.items = [...list].sort(sortAscendingPriority);
845 }
846 /**
847 * Search breakpoints by alias (e.g. gt-xs)
848 */
849 findByAlias(alias) {
850 return !alias ? null : this.findWithPredicate(alias, (bp) => bp.alias === alias);
851 }
852 findByQuery(query) {
853 return this.findWithPredicate(query, (bp) => bp.mediaQuery === query);
854 }
855 /**
856 * Get all the breakpoints whose ranges could overlapping `normal` ranges;
857 * e.g. gt-sm overlaps md, lg, and xl
858 */
859 get overlappings() {
860 return this.items.filter(it => it.overlapping);
861 }
862 /**
863 * Get list of all registered (non-empty) breakpoint aliases
864 */
865 get aliases() {
866 return this.items.map(it => it.alias);
867 }
868 /**
869 * Aliases are mapped to properties using suffixes
870 * e.g. 'gt-sm' for property 'layout' uses suffix 'GtSm'
871 * for property layoutGtSM.
872 */
873 get suffixes() {
874 return this.items.map(it => it?.suffix ?? '');
875 }
876 /**
877 * Memoized lookup using custom predicate function
878 */
879 findWithPredicate(key, searchFn) {
880 let response = this.findByMap.get(key);
881 if (!response) {
882 response = this.items.find(searchFn) ?? null;
883 this.findByMap.set(key, response);
884 }
885 return response ?? null;
886 }
887}
888BreakPointRegistry.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: BreakPointRegistry, deps: [{ token: BREAKPOINTS }], target: i0.ɵɵFactoryTarget.Injectable });
889BreakPointRegistry.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: BreakPointRegistry, providedIn: 'root' });
890i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: BreakPointRegistry, decorators: [{
891 type: Injectable,
892 args: [{ providedIn: 'root' }]
893 }], ctorParameters: function () { return [{ type: undefined, decorators: [{
894 type: Inject,
895 args: [BREAKPOINTS]
896 }] }]; } });
897
898/**
899 * @license
900 * Copyright Google LLC All Rights Reserved.
901 *
902 * Use of this source code is governed by an MIT-style license that can be
903 * found in the LICENSE file at https://angular.io/license
904 */
905const PRINT = 'print';
906const BREAKPOINT_PRINT = {
907 alias: PRINT,
908 mediaQuery: PRINT,
909 priority: 1000
910};
911/**
912 * PrintHook - Use to intercept print MediaQuery activations and force
913 * layouts to render with the specified print alias/breakpoint
914 *
915 * Used in MediaMarshaller and MediaObserver
916 */
917class PrintHook {
918 constructor(breakpoints, layoutConfig, _document) {
919 this.breakpoints = breakpoints;
920 this.layoutConfig = layoutConfig;
921 this._document = _document;
922 // registeredBeforeAfterPrintHooks tracks if we registered the `beforeprint`
923 // and `afterprint` event listeners.
924 this.registeredBeforeAfterPrintHooks = false;
925 // isPrintingBeforeAfterEvent is used to track if we are printing from within
926 // a `beforeprint` event handler. This prevents the typical `stopPrinting`
927 // form `interceptEvents` so that printing is not stopped while the dialog
928 // is still open. This is an extension of the `isPrinting` property on
929 // browsers which support `beforeprint` and `afterprint` events.
930 this.isPrintingBeforeAfterEvent = false;
931 this.beforePrintEventListeners = [];
932 this.afterPrintEventListeners = [];
933 this.formerActivations = null;
934 // Is this service currently in print mode
935 this.isPrinting = false;
936 this.queue = new PrintQueue();
937 this.deactivations = [];
938 }
939 /** Add 'print' mediaQuery: to listen for matchMedia activations */
940 withPrintQuery(queries) {
941 return [...queries, PRINT];
942 }
943 /** Is the MediaChange event for any 'print' @media */
944 isPrintEvent(e) {
945 return e.mediaQuery.startsWith(PRINT);
946 }
947 /** What is the desired mqAlias to use while printing? */
948 get printAlias() {
949 return [...(this.layoutConfig.printWithBreakpoints ?? [])];
950 }
951 /** Lookup breakpoints associated with print aliases. */
952 get printBreakPoints() {
953 return this.printAlias
954 .map(alias => this.breakpoints.findByAlias(alias))
955 .filter(bp => bp !== null);
956 }
957 /** Lookup breakpoint associated with mediaQuery */
958 getEventBreakpoints({ mediaQuery }) {
959 const bp = this.breakpoints.findByQuery(mediaQuery);
960 const list = bp ? [...this.printBreakPoints, bp] : this.printBreakPoints;
961 return list.sort(sortDescendingPriority);
962 }
963 /** Update event with printAlias mediaQuery information */
964 updateEvent(event) {
965 let bp = this.breakpoints.findByQuery(event.mediaQuery);
966 if (this.isPrintEvent(event)) {
967 // Reset from 'print' to first (highest priority) print breakpoint
968 bp = this.getEventBreakpoints(event)[0];
969 event.mediaQuery = bp?.mediaQuery ?? '';
970 }
971 return mergeAlias(event, bp);
972 }
973 // registerBeforeAfterPrintHooks registers a `beforeprint` event hook so we can
974 // trigger print styles synchronously and apply proper layout styles.
975 // It is a noop if the hooks have already been registered or if the document's
976 // `defaultView` is not available.
977 registerBeforeAfterPrintHooks(target) {
978 // `defaultView` may be null when rendering on the server or in other contexts.
979 if (!this._document.defaultView || this.registeredBeforeAfterPrintHooks) {
980 return;
981 }
982 this.registeredBeforeAfterPrintHooks = true;
983 const beforePrintListener = () => {
984 // If we aren't already printing, start printing and update the styles as
985 // if there was a regular print `MediaChange`(from matchMedia).
986 if (!this.isPrinting) {
987 this.isPrintingBeforeAfterEvent = true;
988 this.startPrinting(target, this.getEventBreakpoints(new MediaChange(true, PRINT)));
989 target.updateStyles();
990 }
991 };
992 const afterPrintListener = () => {
993 // If we aren't already printing, start printing and update the styles as
994 // if there was a regular print `MediaChange`(from matchMedia).
995 this.isPrintingBeforeAfterEvent = false;
996 if (this.isPrinting) {
997 this.stopPrinting(target);
998 target.updateStyles();
999 }
1000 };
1001 // Could we have teardown logic to remove if there are no print listeners being used?
1002 this._document.defaultView.addEventListener('beforeprint', beforePrintListener);
1003 this._document.defaultView.addEventListener('afterprint', afterPrintListener);
1004 this.beforePrintEventListeners.push(beforePrintListener);
1005 this.afterPrintEventListeners.push(afterPrintListener);
1006 }
1007 /**
1008 * Prepare RxJS tap operator with partial application
1009 * @return pipeable tap predicate
1010 */
1011 interceptEvents(target) {
1012 return (event) => {
1013 if (this.isPrintEvent(event)) {
1014 if (event.matches && !this.isPrinting) {
1015 this.startPrinting(target, this.getEventBreakpoints(event));
1016 target.updateStyles();
1017 }
1018 else if (!event.matches && this.isPrinting && !this.isPrintingBeforeAfterEvent) {
1019 this.stopPrinting(target);
1020 target.updateStyles();
1021 }
1022 return;
1023 }
1024 this.collectActivations(target, event);
1025 };
1026 }
1027 /** Stop mediaChange event propagation in event streams */
1028 blockPropagation() {
1029 return (event) => {
1030 return !(this.isPrinting || this.isPrintEvent(event));
1031 };
1032 }
1033 /**
1034 * Save current activateBreakpoints (for later restore)
1035 * and substitute only the printAlias breakpoint
1036 */
1037 startPrinting(target, bpList) {
1038 this.isPrinting = true;
1039 this.formerActivations = target.activatedBreakpoints;
1040 target.activatedBreakpoints = this.queue.addPrintBreakpoints(bpList);
1041 }
1042 /** For any print de-activations, reset the entire print queue */
1043 stopPrinting(target) {
1044 target.activatedBreakpoints = this.deactivations;
1045 this.deactivations = [];
1046 this.formerActivations = null;
1047 this.queue.clear();
1048 this.isPrinting = false;
1049 }
1050 /**
1051 * To restore pre-Print Activations, we must capture the proper
1052 * list of breakpoint activations BEFORE print starts. OnBeforePrint()
1053 * is supported; so 'print' mediaQuery activations are used as a fallback
1054 * in browsers without `beforeprint` support.
1055 *
1056 * > But activated breakpoints are deactivated BEFORE 'print' activation.
1057 *
1058 * Let's capture all de-activations using the following logic:
1059 *
1060 * When not printing:
1061 * - clear cache when activating non-print breakpoint
1062 * - update cache (and sort) when deactivating
1063 *
1064 * When printing:
1065 * - sort and save when starting print
1066 * - restore as activatedTargets and clear when stop printing
1067 */
1068 collectActivations(target, event) {
1069 if (!this.isPrinting || this.isPrintingBeforeAfterEvent) {
1070 if (!this.isPrintingBeforeAfterEvent) {
1071 // Only clear deactivations if we aren't printing from a `beforeprint` event.
1072 // Otherwise, this will clear before `stopPrinting()` is called to restore
1073 // the pre-Print Activations.
1074 this.deactivations = [];
1075 return;
1076 }
1077 if (!event.matches) {
1078 const bp = this.breakpoints.findByQuery(event.mediaQuery);
1079 // Deactivating a breakpoint
1080 if (bp) {
1081 const hasFormerBp = this.formerActivations && this.formerActivations.includes(bp);
1082 const wasActivated = !this.formerActivations && target.activatedBreakpoints.includes(bp);
1083 const shouldDeactivate = hasFormerBp || wasActivated;
1084 if (shouldDeactivate) {
1085 this.deactivations.push(bp);
1086 this.deactivations.sort(sortDescendingPriority);
1087 }
1088 }
1089 }
1090 }
1091 }
1092 /** Teardown logic for the service. */
1093 ngOnDestroy() {
1094 if (this._document.defaultView) {
1095 this.beforePrintEventListeners.forEach(l => this._document.defaultView.removeEventListener('beforeprint', l));
1096 this.afterPrintEventListeners.forEach(l => this._document.defaultView.removeEventListener('afterprint', l));
1097 }
1098 }
1099}
1100PrintHook.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: PrintHook, deps: [{ token: BreakPointRegistry }, { token: LAYOUT_CONFIG }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
1101PrintHook.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: PrintHook, providedIn: 'root' });
1102i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: PrintHook, decorators: [{
1103 type: Injectable,
1104 args: [{ providedIn: 'root' }]
1105 }], ctorParameters: function () { return [{ type: BreakPointRegistry }, { type: undefined, decorators: [{
1106 type: Inject,
1107 args: [LAYOUT_CONFIG]
1108 }] }, { type: undefined, decorators: [{
1109 type: Inject,
1110 args: [DOCUMENT]
1111 }] }]; } });
1112// ************************************************************************
1113// Internal Utility class 'PrintQueue'
1114// ************************************************************************
1115/**
1116 * Utility class to manage print breakpoints + activatedBreakpoints
1117 * with correct sorting WHILE printing
1118 */
1119class PrintQueue {
1120 constructor() {
1121 /** Sorted queue with prioritized print breakpoints */
1122 this.printBreakpoints = [];
1123 }
1124 addPrintBreakpoints(bpList) {
1125 bpList.push(BREAKPOINT_PRINT);
1126 bpList.sort(sortDescendingPriority);
1127 bpList.forEach(bp => this.addBreakpoint(bp));
1128 return this.printBreakpoints;
1129 }
1130 /** Add Print breakpoint to queue */
1131 addBreakpoint(bp) {
1132 if (!!bp) {
1133 const bpInList = this.printBreakpoints.find(it => it.mediaQuery === bp.mediaQuery);
1134 if (bpInList === undefined) {
1135 // If this is a `printAlias` breakpoint, then append. If a true 'print' breakpoint,
1136 // register as highest priority in the queue
1137 this.printBreakpoints = isPrintBreakPoint(bp) ? [bp, ...this.printBreakpoints]
1138 : [...this.printBreakpoints, bp];
1139 }
1140 }
1141 }
1142 /** Restore original activated breakpoints and clear internal caches */
1143 clear() {
1144 this.printBreakpoints = [];
1145 }
1146}
1147// ************************************************************************
1148// Internal Utility methods
1149// ************************************************************************
1150/** Only support intercept queueing if the Breakpoint is a print @media query */
1151function isPrintBreakPoint(bp) {
1152 return bp?.mediaQuery.startsWith(PRINT) ?? false;
1153}
1154
1155/**
1156 * @license
1157 * Copyright Google LLC All Rights Reserved.
1158 *
1159 * Use of this source code is governed by an MIT-style license that can be
1160 * found in the LICENSE file at https://angular.io/license
1161 */
1162/**
1163 * MediaMarshaller - register responsive values from directives and
1164 * trigger them based on media query events
1165 */
1166class MediaMarshaller {
1167 constructor(matchMedia, breakpoints, hook) {
1168 this.matchMedia = matchMedia;
1169 this.breakpoints = breakpoints;
1170 this.hook = hook;
1171 this._useFallbacks = true;
1172 this._activatedBreakpoints = [];
1173 this.elementMap = new Map();
1174 this.elementKeyMap = new WeakMap();
1175 this.watcherMap = new WeakMap(); // special triggers to update elements
1176 this.updateMap = new WeakMap(); // callback functions to update styles
1177 this.clearMap = new WeakMap(); // callback functions to clear styles
1178 this.subject = new Subject();
1179 this.observeActivations();
1180 }
1181 get activatedAlias() {
1182 return this.activatedBreakpoints[0]?.alias ?? '';
1183 }
1184 set activatedBreakpoints(bps) {
1185 this._activatedBreakpoints = [...bps];
1186 }
1187 get activatedBreakpoints() {
1188 return [...this._activatedBreakpoints];
1189 }
1190 set useFallbacks(value) {
1191 this._useFallbacks = value;
1192 }
1193 /**
1194 * Update styles on breakpoint activates or deactivates
1195 * @param mc
1196 */
1197 onMediaChange(mc) {
1198 const bp = this.findByQuery(mc.mediaQuery);
1199 if (bp) {
1200 mc = mergeAlias(mc, bp);
1201 const bpIndex = this.activatedBreakpoints.indexOf(bp);
1202 if (mc.matches && bpIndex === -1) {
1203 this._activatedBreakpoints.push(bp);
1204 this._activatedBreakpoints.sort(sortDescendingPriority);
1205 this.updateStyles();
1206 }
1207 else if (!mc.matches && bpIndex !== -1) {
1208 // Remove the breakpoint when it's deactivated
1209 this._activatedBreakpoints.splice(bpIndex, 1);
1210 this._activatedBreakpoints.sort(sortDescendingPriority);
1211 this.updateStyles();
1212 }
1213 }
1214 }
1215 /**
1216 * initialize the marshaller with necessary elements for delegation on an element
1217 * @param element
1218 * @param key
1219 * @param updateFn optional callback so that custom bp directives don't have to re-provide this
1220 * @param clearFn optional callback so that custom bp directives don't have to re-provide this
1221 * @param extraTriggers other triggers to force style updates (e.g. layout, directionality, etc)
1222 */
1223 init(element, key, updateFn, clearFn, extraTriggers = []) {
1224 initBuilderMap(this.updateMap, element, key, updateFn);
1225 initBuilderMap(this.clearMap, element, key, clearFn);
1226 this.buildElementKeyMap(element, key);
1227 this.watchExtraTriggers(element, key, extraTriggers);
1228 }
1229 /**
1230 * get the value for an element and key and optionally a given breakpoint
1231 * @param element
1232 * @param key
1233 * @param bp
1234 */
1235 getValue(element, key, bp) {
1236 const bpMap = this.elementMap.get(element);
1237 if (bpMap) {
1238 const values = bp !== undefined ? bpMap.get(bp) : this.getActivatedValues(bpMap, key);
1239 if (values) {
1240 return values.get(key);
1241 }
1242 }
1243 return undefined;
1244 }
1245 /**
1246 * whether the element has values for a given key
1247 * @param element
1248 * @param key
1249 */
1250 hasValue(element, key) {
1251 const bpMap = this.elementMap.get(element);
1252 if (bpMap) {
1253 const values = this.getActivatedValues(bpMap, key);
1254 if (values) {
1255 return values.get(key) !== undefined || false;
1256 }
1257 }
1258 return false;
1259 }
1260 /**
1261 * Set the value for an input on a directive
1262 * @param element the element in question
1263 * @param key the type of the directive (e.g. flex, layout-gap, etc)
1264 * @param bp the breakpoint suffix (empty string = default)
1265 * @param val the value for the breakpoint
1266 */
1267 setValue(element, key, val, bp) {
1268 let bpMap = this.elementMap.get(element);
1269 if (!bpMap) {
1270 bpMap = new Map().set(bp, new Map().set(key, val));
1271 this.elementMap.set(element, bpMap);
1272 }
1273 else {
1274 const values = (bpMap.get(bp) ?? new Map()).set(key, val);
1275 bpMap.set(bp, values);
1276 this.elementMap.set(element, bpMap);
1277 }
1278 const value = this.getValue(element, key);
1279 if (value !== undefined) {
1280 this.updateElement(element, key, value);
1281 }
1282 }
1283 /** Track element value changes for a specific key */
1284 trackValue(element, key) {
1285 return this.subject
1286 .asObservable()
1287 .pipe(filter(v => v.element === element && v.key === key));
1288 }
1289 /** update all styles for all elements on the current breakpoint */
1290 updateStyles() {
1291 this.elementMap.forEach((bpMap, el) => {
1292 const keyMap = new Set(this.elementKeyMap.get(el));
1293 let valueMap = this.getActivatedValues(bpMap);
1294 if (valueMap) {
1295 valueMap.forEach((v, k) => {
1296 this.updateElement(el, k, v);
1297 keyMap.delete(k);
1298 });
1299 }
1300 keyMap.forEach(k => {
1301 valueMap = this.getActivatedValues(bpMap, k);
1302 if (valueMap) {
1303 const value = valueMap.get(k);
1304 this.updateElement(el, k, value);
1305 }
1306 else {
1307 this.clearElement(el, k);
1308 }
1309 });
1310 });
1311 }
1312 /**
1313 * clear the styles for a given element
1314 * @param element
1315 * @param key
1316 */
1317 clearElement(element, key) {
1318 const builders = this.clearMap.get(element);
1319 if (builders) {
1320 const clearFn = builders.get(key);
1321 if (!!clearFn) {
1322 clearFn();
1323 this.subject.next({ element, key, value: '' });
1324 }
1325 }
1326 }
1327 /**
1328 * update a given element with the activated values for a given key
1329 * @param element
1330 * @param key
1331 * @param value
1332 */
1333 updateElement(element, key, value) {
1334 const builders = this.updateMap.get(element);
1335 if (builders) {
1336 const updateFn = builders.get(key);
1337 if (!!updateFn) {
1338 updateFn(value);
1339 this.subject.next({ element, key, value });
1340 }
1341 }
1342 }
1343 /**
1344 * release all references to a given element
1345 * @param element
1346 */
1347 releaseElement(element) {
1348 const watcherMap = this.watcherMap.get(element);
1349 if (watcherMap) {
1350 watcherMap.forEach(s => s.unsubscribe());
1351 this.watcherMap.delete(element);
1352 }
1353 const elementMap = this.elementMap.get(element);
1354 if (elementMap) {
1355 elementMap.forEach((_, s) => elementMap.delete(s));
1356 this.elementMap.delete(element);
1357 }
1358 }
1359 /**
1360 * trigger an update for a given element and key (e.g. layout)
1361 * @param element
1362 * @param key
1363 */
1364 triggerUpdate(element, key) {
1365 const bpMap = this.elementMap.get(element);
1366 if (bpMap) {
1367 const valueMap = this.getActivatedValues(bpMap, key);
1368 if (valueMap) {
1369 if (key) {
1370 this.updateElement(element, key, valueMap.get(key));
1371 }
1372 else {
1373 valueMap.forEach((v, k) => this.updateElement(element, k, v));
1374 }
1375 }
1376 }
1377 }
1378 /** Cross-reference for HTMLElement with directive key */
1379 buildElementKeyMap(element, key) {
1380 let keyMap = this.elementKeyMap.get(element);
1381 if (!keyMap) {
1382 keyMap = new Set();
1383 this.elementKeyMap.set(element, keyMap);
1384 }
1385 keyMap.add(key);
1386 }
1387 /**
1388 * Other triggers that should force style updates:
1389 * - directionality
1390 * - layout changes
1391 * - mutationobserver updates
1392 */
1393 watchExtraTriggers(element, key, triggers) {
1394 if (triggers && triggers.length) {
1395 let watchers = this.watcherMap.get(element);
1396 if (!watchers) {
1397 watchers = new Map();
1398 this.watcherMap.set(element, watchers);
1399 }
1400 const subscription = watchers.get(key);
1401 if (!subscription) {
1402 const newSubscription = merge(...triggers).subscribe(() => {
1403 const currentValue = this.getValue(element, key);
1404 this.updateElement(element, key, currentValue);
1405 });
1406 watchers.set(key, newSubscription);
1407 }
1408 }
1409 }
1410 /** Breakpoint locator by mediaQuery */
1411 findByQuery(query) {
1412 return this.breakpoints.findByQuery(query);
1413 }
1414 /**
1415 * get the fallback breakpoint for a given element, starting with the current breakpoint
1416 * @param bpMap
1417 * @param key
1418 */
1419 getActivatedValues(bpMap, key) {
1420 for (let i = 0; i < this.activatedBreakpoints.length; i++) {
1421 const activatedBp = this.activatedBreakpoints[i];
1422 const valueMap = bpMap.get(activatedBp.alias);
1423 if (valueMap) {
1424 if (key === undefined || (valueMap.has(key) && valueMap.get(key) != null)) {
1425 return valueMap;
1426 }
1427 }
1428 }
1429 // On the server, we explicitly have an "all" section filled in to begin with.
1430 // So we don't need to aggressively find a fallback if no explicit value exists.
1431 if (!this._useFallbacks) {
1432 return undefined;
1433 }
1434 const lastHope = bpMap.get('');
1435 return (key === undefined || lastHope && lastHope.has(key)) ? lastHope : undefined;
1436 }
1437 /**
1438 * Watch for mediaQuery breakpoint activations
1439 */
1440 observeActivations() {
1441 const queries = this.breakpoints.items.map(bp => bp.mediaQuery);
1442 this.hook.registerBeforeAfterPrintHooks(this);
1443 this.matchMedia
1444 .observe(this.hook.withPrintQuery(queries))
1445 .pipe(tap(this.hook.interceptEvents(this)), filter(this.hook.blockPropagation()))
1446 .subscribe(this.onMediaChange.bind(this));
1447 }
1448}
1449MediaMarshaller.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaMarshaller, deps: [{ token: MatchMedia }, { token: BreakPointRegistry }, { token: PrintHook }], target: i0.ɵɵFactoryTarget.Injectable });
1450MediaMarshaller.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaMarshaller, providedIn: 'root' });
1451i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaMarshaller, decorators: [{
1452 type: Injectable,
1453 args: [{ providedIn: 'root' }]
1454 }], ctorParameters: function () { return [{ type: MatchMedia }, { type: BreakPointRegistry }, { type: PrintHook }]; } });
1455function initBuilderMap(map, element, key, input) {
1456 if (input !== undefined) {
1457 const oldMap = map.get(element) ?? new Map();
1458 oldMap.set(key, input);
1459 map.set(element, oldMap);
1460 }
1461}
1462
1463/**
1464 * @license
1465 * Copyright Google LLC All Rights Reserved.
1466 *
1467 * Use of this source code is governed by an MIT-style license that can be
1468 * found in the LICENSE file at https://angular.io/license
1469 */
1470class BaseDirective2 {
1471 constructor(elementRef, styleBuilder, styler, marshal) {
1472 this.elementRef = elementRef;
1473 this.styleBuilder = styleBuilder;
1474 this.styler = styler;
1475 this.marshal = marshal;
1476 this.DIRECTIVE_KEY = '';
1477 this.inputs = [];
1478 /** The most recently used styles for the builder */
1479 this.mru = {};
1480 this.destroySubject = new Subject();
1481 /** Cache map for style computation */
1482 this.styleCache = new Map();
1483 }
1484 /** Access to host element's parent DOM node */
1485 get parentElement() {
1486 return this.elementRef.nativeElement.parentElement;
1487 }
1488 /** Access to the HTMLElement for the directive */
1489 get nativeElement() {
1490 return this.elementRef.nativeElement;
1491 }
1492 /** Access to the activated value for the directive */
1493 get activatedValue() {
1494 return this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY);
1495 }
1496 set activatedValue(value) {
1497 this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, value, this.marshal.activatedAlias);
1498 }
1499 /** For @Input changes */
1500 ngOnChanges(changes) {
1501 Object.keys(changes).forEach(key => {
1502 if (this.inputs.indexOf(key) !== -1) {
1503 const bp = key.split('.').slice(1).join('.');
1504 const val = changes[key].currentValue;
1505 this.setValue(val, bp);
1506 }
1507 });
1508 }
1509 ngOnDestroy() {
1510 this.destroySubject.next();
1511 this.destroySubject.complete();
1512 this.marshal.releaseElement(this.nativeElement);
1513 }
1514 /** Register with central marshaller service */
1515 init(extraTriggers = []) {
1516 this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, this.updateWithValue.bind(this), this.clearStyles.bind(this), extraTriggers);
1517 }
1518 /** Add styles to the element using predefined style builder */
1519 addStyles(input, parent) {
1520 const builder = this.styleBuilder;
1521 const useCache = builder.shouldCache;
1522 let genStyles = this.styleCache.get(input);
1523 if (!genStyles || !useCache) {
1524 genStyles = builder.buildStyles(input, parent);
1525 if (useCache) {
1526 this.styleCache.set(input, genStyles);
1527 }
1528 }
1529 this.mru = { ...genStyles };
1530 this.applyStyleToElement(genStyles);
1531 builder.sideEffect(input, genStyles, parent);
1532 }
1533 /** Remove generated styles from an element using predefined style builder */
1534 clearStyles() {
1535 Object.keys(this.mru).forEach(k => {
1536 this.mru[k] = '';
1537 });
1538 this.applyStyleToElement(this.mru);
1539 this.mru = {};
1540 this.currentValue = undefined;
1541 }
1542 /** Force trigger style updates on DOM element */
1543 triggerUpdate() {
1544 this.marshal.triggerUpdate(this.nativeElement, this.DIRECTIVE_KEY);
1545 }
1546 /**
1547 * Determine the DOM element's Flexbox flow (flex-direction).
1548 *
1549 * Check inline style first then check computed (stylesheet) style.
1550 * And optionally add the flow value to element's inline style.
1551 */
1552 getFlexFlowDirection(target, addIfMissing = false) {
1553 if (target) {
1554 const [value, hasInlineValue] = this.styler.getFlowDirection(target);
1555 if (!hasInlineValue && addIfMissing) {
1556 const style = buildLayoutCSS(value);
1557 const elements = [target];
1558 this.styler.applyStyleToElements(style, elements);
1559 }
1560 return value.trim();
1561 }
1562 return 'row';
1563 }
1564 hasWrap(target) {
1565 return this.styler.hasWrap(target);
1566 }
1567 /** Applies styles given via string pair or object map to the directive element */
1568 applyStyleToElement(style, value, element = this.nativeElement) {
1569 this.styler.applyStyleToElement(element, style, value);
1570 }
1571 setValue(val, bp) {
1572 this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, val, bp);
1573 }
1574 updateWithValue(input) {
1575 if (this.currentValue !== input) {
1576 this.addStyles(input);
1577 this.currentValue = input;
1578 }
1579 }
1580}
1581BaseDirective2.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: BaseDirective2, deps: [{ token: i0.ElementRef }, { token: StyleBuilder }, { token: StyleUtils }, { token: MediaMarshaller }], target: i0.ɵɵFactoryTarget.Directive });
1582BaseDirective2.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: BaseDirective2, usesOnChanges: true, ngImport: i0 });
1583i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: BaseDirective2, decorators: [{
1584 type: Directive
1585 }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: StyleBuilder }, { type: StyleUtils }, { type: MediaMarshaller }]; } });
1586
1587/**
1588 * @license
1589 * Copyright Google LLC All Rights Reserved.
1590 *
1591 * Use of this source code is governed by an MIT-style license that can be
1592 * found in the LICENSE file at https://angular.io/license
1593 */
1594
1595/**
1596 * @license
1597 * Copyright Google LLC All Rights Reserved.
1598 *
1599 * Use of this source code is governed by an MIT-style license that can be
1600 * found in the LICENSE file at https://angular.io/license
1601 */
1602
1603/**
1604 * @license
1605 * Copyright Google LLC All Rights Reserved.
1606 *
1607 * Use of this source code is governed by an MIT-style license that can be
1608 * found in the LICENSE file at https://angular.io/license
1609 */
1610/**
1611 * MockMatchMedia mocks calls to the Window API matchMedia with a build of a simulated
1612 * MockMediaQueryListener. Methods are available to simulate an activation of a mediaQuery
1613 * range and to clearAll mediaQuery listeners.
1614 */
1615class MockMatchMedia extends MatchMedia {
1616 constructor(_zone, _platformId, _document, _breakpoints) {
1617 super(_zone, _platformId, _document);
1618 this._breakpoints = _breakpoints;
1619 this.autoRegisterQueries = true; // Used for testing BreakPoint registrations
1620 this.useOverlaps = false; // Allow fallback to overlapping mediaQueries
1621 }
1622 /** Easy method to clear all listeners for all mediaQueries */
1623 clearAll() {
1624 this.registry.forEach((mql) => {
1625 mql.destroy();
1626 });
1627 this.registry.clear();
1628 this.useOverlaps = false;
1629 }
1630 /** Feature to support manual, simulated activation of a mediaQuery. */
1631 activate(mediaQuery, useOverlaps = this.useOverlaps) {
1632 mediaQuery = this._validateQuery(mediaQuery);
1633 if (useOverlaps || !this.isActive(mediaQuery)) {
1634 this._deactivateAll();
1635 this._registerMediaQuery(mediaQuery);
1636 this._activateWithOverlaps(mediaQuery, useOverlaps);
1637 }
1638 return this.hasActivated;
1639 }
1640 /** Converts an optional mediaQuery alias to a specific, valid mediaQuery */
1641 _validateQuery(queryOrAlias) {
1642 const bp = this._breakpoints.findByAlias(queryOrAlias);
1643 return bp?.mediaQuery ?? queryOrAlias;
1644 }
1645 /**
1646 * Manually onMediaChange any overlapping mediaQueries to simulate
1647 * similar functionality in the window.matchMedia()
1648 */
1649 _activateWithOverlaps(mediaQuery, useOverlaps) {
1650 if (useOverlaps) {
1651 const bp = this._breakpoints.findByQuery(mediaQuery);
1652 const alias = bp?.alias ?? 'unknown';
1653 // Simulate activation of overlapping lt-<XXX> ranges
1654 switch (alias) {
1655 case 'lg':
1656 this._activateByAlias(['lt-xl']);
1657 break;
1658 case 'md':
1659 this._activateByAlias(['lt-xl', 'lt-lg']);
1660 break;
1661 case 'sm':
1662 this._activateByAlias(['lt-xl', 'lt-lg', 'lt-md']);
1663 break;
1664 case 'xs':
1665 this._activateByAlias(['lt-xl', 'lt-lg', 'lt-md', 'lt-sm']);
1666 break;
1667 }
1668 // Simulate activation of overlapping gt-<xxxx> mediaQuery ranges
1669 switch (alias) {
1670 case 'xl':
1671 this._activateByAlias(['gt-lg', 'gt-md', 'gt-sm', 'gt-xs']);
1672 break;
1673 case 'lg':
1674 this._activateByAlias(['gt-md', 'gt-sm', 'gt-xs']);
1675 break;
1676 case 'md':
1677 this._activateByAlias(['gt-sm', 'gt-xs']);
1678 break;
1679 case 'sm':
1680 this._activateByAlias(['gt-xs']);
1681 break;
1682 }
1683 }
1684 // Activate last since the responsiveActivation is watching *this* mediaQuery
1685 return this._activateByQuery(mediaQuery);
1686 }
1687 /**
1688 *
1689 */
1690 _activateByAlias(aliases) {
1691 const activate = (alias) => {
1692 const bp = this._breakpoints.findByAlias(alias);
1693 this._activateByQuery(bp?.mediaQuery ?? alias);
1694 };
1695 aliases.forEach(activate);
1696 }
1697 /**
1698 *
1699 */
1700 _activateByQuery(mediaQuery) {
1701 if (!this.registry.has(mediaQuery) && this.autoRegisterQueries) {
1702 this._registerMediaQuery(mediaQuery);
1703 }
1704 const mql = this.registry.get(mediaQuery);
1705 if (mql && !this.isActive(mediaQuery)) {
1706 this.registry.set(mediaQuery, mql.activate());
1707 }
1708 return this.hasActivated;
1709 }
1710 /** Deactivate all current MQLs and reset the buffer */
1711 _deactivateAll() {
1712 this.registry.forEach((it) => {
1713 it.deactivate();
1714 });
1715 return this;
1716 }
1717 /** Insure the mediaQuery is registered with MatchMedia */
1718 _registerMediaQuery(mediaQuery) {
1719 if (!this.registry.has(mediaQuery) && this.autoRegisterQueries) {
1720 this.registerQuery(mediaQuery);
1721 }
1722 }
1723 /**
1724 * Call window.matchMedia() to build a MediaQueryList; which
1725 * supports 0..n listeners for activation/deactivation
1726 */
1727 buildMQL(query) {
1728 return new MockMediaQueryList(query);
1729 }
1730 get hasActivated() {
1731 return this.activations.length > 0;
1732 }
1733}
1734MockMatchMedia.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MockMatchMedia, deps: [{ token: i0.NgZone }, { token: PLATFORM_ID }, { token: DOCUMENT }, { token: BreakPointRegistry }], target: i0.ɵɵFactoryTarget.Injectable });
1735MockMatchMedia.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MockMatchMedia });
1736i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MockMatchMedia, decorators: [{
1737 type: Injectable
1738 }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: Object, decorators: [{
1739 type: Inject,
1740 args: [PLATFORM_ID]
1741 }] }, { type: undefined, decorators: [{
1742 type: Inject,
1743 args: [DOCUMENT]
1744 }] }, { type: BreakPointRegistry }]; } });
1745/**
1746 * Special internal class to simulate a MediaQueryList and
1747 * - supports manual activation to simulate mediaQuery matching
1748 * - manages listeners
1749 */
1750class MockMediaQueryList {
1751 constructor(_mediaQuery) {
1752 this._mediaQuery = _mediaQuery;
1753 this._isActive = false;
1754 this._listeners = [];
1755 this.onchange = null;
1756 }
1757 get matches() {
1758 return this._isActive;
1759 }
1760 get media() {
1761 return this._mediaQuery;
1762 }
1763 /**
1764 * Destroy the current list by deactivating the
1765 * listeners and clearing the internal list
1766 */
1767 destroy() {
1768 this.deactivate();
1769 this._listeners = [];
1770 }
1771 /** Notify all listeners that 'matches === TRUE' */
1772 activate() {
1773 if (!this._isActive) {
1774 this._isActive = true;
1775 this._listeners.forEach((callback) => {
1776 const cb = callback;
1777 cb.call(this, { matches: this.matches, media: this.media });
1778 });
1779 }
1780 return this;
1781 }
1782 /** Notify all listeners that 'matches === false' */
1783 deactivate() {
1784 if (this._isActive) {
1785 this._isActive = false;
1786 this._listeners.forEach((callback) => {
1787 const cb = callback;
1788 cb.call(this, { matches: this.matches, media: this.media });
1789 });
1790 }
1791 return this;
1792 }
1793 /** Add a listener to our internal list to activate later */
1794 addListener(listener) {
1795 if (this._listeners.indexOf(listener) === -1) {
1796 this._listeners.push(listener);
1797 }
1798 if (this._isActive) {
1799 const cb = listener;
1800 cb.call(this, { matches: this.matches, media: this.media });
1801 }
1802 }
1803 /** Don't need to remove listeners in the testing environment */
1804 removeListener(_) {
1805 }
1806 addEventListener(_, __, ___) {
1807 }
1808 removeEventListener(_, __, ___) {
1809 }
1810 dispatchEvent(_) {
1811 return false;
1812 }
1813}
1814/**
1815 * Pre-configured provider for MockMatchMedia
1816 */
1817const MockMatchMediaProvider = {
1818 provide: MatchMedia,
1819 useClass: MockMatchMedia
1820};
1821
1822/**
1823 * @license
1824 * Copyright Google LLC All Rights Reserved.
1825 *
1826 * Use of this source code is governed by an MIT-style license that can be
1827 * found in the LICENSE file at https://angular.io/license
1828 */
1829
1830/**
1831 * @license
1832 * Copyright Google LLC All Rights Reserved.
1833 *
1834 * Use of this source code is governed by an MIT-style license that can be
1835 * found in the LICENSE file at https://angular.io/license
1836 */
1837/** Wraps the provided value in an array, unless the provided value is an array. */
1838function coerceArray(value) {
1839 return Array.isArray(value) ? value : [value];
1840}
1841
1842/**
1843 * @license
1844 * Copyright Google LLC All Rights Reserved.
1845 *
1846 * Use of this source code is governed by an MIT-style license that can be
1847 * found in the LICENSE file at https://angular.io/license
1848 */
1849/**
1850 * MediaObserver enables applications to listen for 1..n mediaQuery activations and to determine
1851 * if a mediaQuery is currently activated.
1852 *
1853 * Since a breakpoint change will first deactivate 1...n mediaQueries and then possibly activate
1854 * 1..n mediaQueries, the MediaObserver will debounce notifications and report ALL *activations*
1855 * in 1 event notification. The reported activations will be sorted in descending priority order.
1856 *
1857 * This class uses the BreakPoint Registry to inject alias information into the raw MediaChange
1858 * notification. For custom mediaQuery notifications, alias information will not be injected and
1859 * those fields will be ''.
1860 *
1861 * Note: Developers should note that only mediaChange activations (not de-activations)
1862 * are announced by the MediaObserver.
1863 *
1864 * @usage
1865 *
1866 * // RxJS
1867 * import { filter } from 'rxjs/operators';
1868 * import { MediaObserver } from '@angular/flex-layout';
1869 *
1870 * @Component({ ... })
1871 * export class AppComponent {
1872 * status: string = '';
1873 *
1874 * constructor(mediaObserver: MediaObserver) {
1875 * const media$ = mediaObserver.asObservable().pipe(
1876 * filter((changes: MediaChange[]) => true) // silly noop filter
1877 * );
1878 *
1879 * media$.subscribe((changes: MediaChange[]) => {
1880 * let status = '';
1881 * changes.forEach( change => {
1882 * status += `'${change.mqAlias}' = (${change.mediaQuery}) <br/>` ;
1883 * });
1884 * this.status = status;
1885 * });
1886 *
1887 * }
1888 * }
1889 */
1890class MediaObserver {
1891 constructor(breakpoints, matchMedia, hook) {
1892 this.breakpoints = breakpoints;
1893 this.matchMedia = matchMedia;
1894 this.hook = hook;
1895 /** Filter MediaChange notifications for overlapping breakpoints */
1896 this.filterOverlaps = false;
1897 this.destroyed$ = new Subject();
1898 this._media$ = this.watchActivations();
1899 }
1900 /**
1901 * Completes the active subject, signalling to all complete for all
1902 * MediaObserver subscribers
1903 */
1904 ngOnDestroy() {
1905 this.destroyed$.next();
1906 this.destroyed$.complete();
1907 }
1908 // ************************************************
1909 // Public Methods
1910 // ************************************************
1911 /**
1912 * Observe changes to current activation 'list'
1913 */
1914 asObservable() {
1915 return this._media$;
1916 }
1917 /**
1918 * Allow programmatic query to determine if one or more media query/alias match
1919 * the current viewport size.
1920 * @param value One or more media queries (or aliases) to check.
1921 * @returns Whether any of the media queries match.
1922 */
1923 isActive(value) {
1924 const aliases = splitQueries(coerceArray(value));
1925 return aliases.some(alias => {
1926 const query = toMediaQuery(alias, this.breakpoints);
1927 return query !== null && this.matchMedia.isActive(query);
1928 });
1929 }
1930 // ************************************************
1931 // Internal Methods
1932 // ************************************************
1933 /**
1934 * Register all the mediaQueries registered in the BreakPointRegistry
1935 * This is needed so subscribers can be auto-notified of all standard, registered
1936 * mediaQuery activations
1937 */
1938 watchActivations() {
1939 const queries = this.breakpoints.items.map(bp => bp.mediaQuery);
1940 return this.buildObservable(queries);
1941 }
1942 /**
1943 * Only pass/announce activations (not de-activations)
1944 *
1945 * Since multiple-mediaQueries can be activation in a cycle,
1946 * gather all current activations into a single list of changes to observers
1947 *
1948 * Inject associated (if any) alias information into the MediaChange event
1949 * - Exclude mediaQuery activations for overlapping mQs. List bounded mQ ranges only
1950 * - Exclude print activations that do not have an associated mediaQuery
1951 *
1952 * NOTE: the raw MediaChange events [from MatchMedia] do not
1953 * contain important alias information; as such this info
1954 * must be injected into the MediaChange
1955 */
1956 buildObservable(mqList) {
1957 const hasChanges = (changes) => {
1958 const isValidQuery = (change) => (change.mediaQuery.length > 0);
1959 return (changes.filter(isValidQuery).length > 0);
1960 };
1961 const excludeOverlaps = (changes) => {
1962 return !this.filterOverlaps ? changes : changes.filter(change => {
1963 const bp = this.breakpoints.findByQuery(change.mediaQuery);
1964 return bp?.overlapping ?? true;
1965 });
1966 };
1967 const ignoreDuplicates = (previous, current) => {
1968 if (previous.length !== current.length) {
1969 return false;
1970 }
1971 const previousMqs = previous.map(mc => mc.mediaQuery);
1972 const currentMqs = new Set(current.map(mc => mc.mediaQuery));
1973 const difference = new Set(previousMqs.filter(mq => !currentMqs.has(mq)));
1974 return difference.size === 0;
1975 };
1976 /**
1977 */
1978 return this.matchMedia
1979 .observe(this.hook.withPrintQuery(mqList))
1980 .pipe(filter((change) => change.matches), debounceTime(0, asapScheduler), switchMap(_ => of(this.findAllActivations())), map(excludeOverlaps), filter(hasChanges), distinctUntilChanged(ignoreDuplicates), takeUntil(this.destroyed$));
1981 }
1982 /**
1983 * Find all current activations and prepare single list of activations
1984 * sorted by descending priority.
1985 */
1986 findAllActivations() {
1987 const mergeMQAlias = (change) => {
1988 const bp = this.breakpoints.findByQuery(change.mediaQuery);
1989 return mergeAlias(change, bp);
1990 };
1991 const replaceWithPrintAlias = (change) => this.hook.isPrintEvent(change) ? this.hook.updateEvent(change) : change;
1992 return this.matchMedia
1993 .activations
1994 .map(query => new MediaChange(true, query))
1995 .map(replaceWithPrintAlias)
1996 .map(mergeMQAlias)
1997 .sort(sortDescendingPriority);
1998 }
1999}
2000MediaObserver.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaObserver, deps: [{ token: BreakPointRegistry }, { token: MatchMedia }, { token: PrintHook }], target: i0.ɵɵFactoryTarget.Injectable });
2001MediaObserver.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaObserver, providedIn: 'root' });
2002i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaObserver, decorators: [{
2003 type: Injectable,
2004 args: [{ providedIn: 'root' }]
2005 }], ctorParameters: function () { return [{ type: BreakPointRegistry }, { type: MatchMedia }, { type: PrintHook }]; } });
2006/**
2007 * Find associated breakpoint (if any)
2008 */
2009function toMediaQuery(query, locator) {
2010 const bp = locator.findByAlias(query) ?? locator.findByQuery(query);
2011 return bp?.mediaQuery ?? null;
2012}
2013/**
2014 * Split each query string into separate query strings if two queries are provided as comma
2015 * separated.
2016 */
2017function splitQueries(queries) {
2018 return queries.flatMap(query => query.split(','))
2019 .map(query => query.trim());
2020}
2021
2022/**
2023 * @license
2024 * Copyright Google LLC All Rights Reserved.
2025 *
2026 * Use of this source code is governed by an MIT-style license that can be
2027 * found in the LICENSE file at https://angular.io/license
2028 */
2029
2030/**
2031 * @license
2032 * Copyright Google LLC All Rights Reserved.
2033 *
2034 * Use of this source code is governed by an MIT-style license that can be
2035 * found in the LICENSE file at https://angular.io/license
2036 */
2037/**
2038 * Class
2039 */
2040class MediaTrigger {
2041 constructor(breakpoints, matchMedia, layoutConfig, _platformId, _document) {
2042 this.breakpoints = breakpoints;
2043 this.matchMedia = matchMedia;
2044 this.layoutConfig = layoutConfig;
2045 this._platformId = _platformId;
2046 this._document = _document;
2047 this.hasCachedRegistryMatches = false;
2048 this.originalActivations = [];
2049 this.originalRegistry = new Map();
2050 }
2051 /**
2052 * Manually activate range of breakpoints
2053 * @param list array of mediaQuery or alias strings
2054 */
2055 activate(list) {
2056 list = list.map(it => it.trim()); // trim queries
2057 this.saveActivations();
2058 this.deactivateAll();
2059 this.setActivations(list);
2060 this.prepareAutoRestore();
2061 }
2062 /**
2063 * Restore original, 'real' breakpoints and emit events
2064 * to trigger stream notification
2065 */
2066 restore() {
2067 if (this.hasCachedRegistryMatches) {
2068 const extractQuery = (change) => change.mediaQuery;
2069 const list = this.originalActivations.map(extractQuery);
2070 try {
2071 this.deactivateAll();
2072 this.restoreRegistryMatches();
2073 this.setActivations(list);
2074 }
2075 finally {
2076 this.originalActivations = [];
2077 if (this.resizeSubscription) {
2078 this.resizeSubscription.unsubscribe();
2079 }
2080 }
2081 }
2082 }
2083 // ************************************************
2084 // Internal Methods
2085 // ************************************************
2086 /**
2087 * Whenever window resizes, immediately auto-restore original
2088 * activations (if we are simulating activations)
2089 */
2090 prepareAutoRestore() {
2091 const isBrowser = isPlatformBrowser(this._platformId) && this._document;
2092 const enableAutoRestore = isBrowser && this.layoutConfig.mediaTriggerAutoRestore;
2093 if (enableAutoRestore) {
2094 const resize$ = fromEvent(window, 'resize').pipe(take(1));
2095 this.resizeSubscription = resize$.subscribe(this.restore.bind(this));
2096 }
2097 }
2098 /**
2099 * Notify all matchMedia subscribers of de-activations
2100 *
2101 * Note: we must force 'matches' updates for
2102 * future matchMedia::activation lookups
2103 */
2104 deactivateAll() {
2105 const list = this.currentActivations;
2106 this.forceRegistryMatches(list, false);
2107 this.simulateMediaChanges(list, false);
2108 }
2109 /**
2110 * Cache current activations as sorted, prioritized list of MediaChanges
2111 */
2112 saveActivations() {
2113 if (!this.hasCachedRegistryMatches) {
2114 const toMediaChange = (query) => new MediaChange(true, query);
2115 const mergeMQAlias = (change) => {
2116 const bp = this.breakpoints.findByQuery(change.mediaQuery);
2117 return mergeAlias(change, bp);
2118 };
2119 this.originalActivations = this.currentActivations
2120 .map(toMediaChange)
2121 .map(mergeMQAlias)
2122 .sort(sortDescendingPriority);
2123 this.cacheRegistryMatches();
2124 }
2125 }
2126 /**
2127 * Force set manual activations for specified mediaQuery list
2128 */
2129 setActivations(list) {
2130 if (!!this.originalRegistry) {
2131 this.forceRegistryMatches(list, true);
2132 }
2133 this.simulateMediaChanges(list);
2134 }
2135 /**
2136 * For specified mediaQuery list manually simulate activations or deactivations
2137 */
2138 simulateMediaChanges(queries, matches = true) {
2139 const toMediaQuery = (query) => {
2140 const locator = this.breakpoints;
2141 const bp = locator.findByAlias(query) || locator.findByQuery(query);
2142 return bp ? bp.mediaQuery : query;
2143 };
2144 const emitChangeEvent = (query) => this.emitChangeEvent(matches, query);
2145 queries.map(toMediaQuery).forEach(emitChangeEvent);
2146 }
2147 /**
2148 * Replace current registry with simulated registry...
2149 * Note: this is required since MediaQueryList::matches is 'readOnly'
2150 */
2151 forceRegistryMatches(queries, matches) {
2152 const registry = new Map();
2153 queries.forEach(query => {
2154 registry.set(query, { matches });
2155 });
2156 this.matchMedia.registry = registry;
2157 }
2158 /**
2159 * Save current MatchMedia::registry items.
2160 */
2161 cacheRegistryMatches() {
2162 const target = this.originalRegistry;
2163 target.clear();
2164 this.matchMedia.registry.forEach((value, key) => {
2165 target.set(key, value);
2166 });
2167 this.hasCachedRegistryMatches = true;
2168 }
2169 /**
2170 * Restore original, 'true' registry
2171 */
2172 restoreRegistryMatches() {
2173 const target = this.matchMedia.registry;
2174 target.clear();
2175 this.originalRegistry.forEach((value, key) => {
2176 target.set(key, value);
2177 });
2178 this.originalRegistry.clear();
2179 this.hasCachedRegistryMatches = false;
2180 }
2181 /**
2182 * Manually emit a MediaChange event via the MatchMedia to MediaMarshaller and MediaObserver
2183 */
2184 emitChangeEvent(matches, query) {
2185 this.matchMedia.source.next(new MediaChange(matches, query));
2186 }
2187 get currentActivations() {
2188 return this.matchMedia.activations;
2189 }
2190}
2191MediaTrigger.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaTrigger, deps: [{ token: BreakPointRegistry }, { token: MatchMedia }, { token: LAYOUT_CONFIG }, { token: PLATFORM_ID }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
2192MediaTrigger.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaTrigger, providedIn: 'root' });
2193i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MediaTrigger, decorators: [{
2194 type: Injectable,
2195 args: [{ providedIn: 'root' }]
2196 }], ctorParameters: function () { return [{ type: BreakPointRegistry }, { type: MatchMedia }, { type: undefined, decorators: [{
2197 type: Inject,
2198 args: [LAYOUT_CONFIG]
2199 }] }, { type: Object, decorators: [{
2200 type: Inject,
2201 args: [PLATFORM_ID]
2202 }] }, { type: undefined, decorators: [{
2203 type: Inject,
2204 args: [DOCUMENT]
2205 }] }]; } });
2206
2207/**
2208 * @license
2209 * Copyright Google LLC All Rights Reserved.
2210 *
2211 * Use of this source code is governed by an MIT-style license that can be
2212 * found in the LICENSE file at https://angular.io/license
2213 */
2214
2215/**
2216 * @license
2217 * Copyright Google LLC All Rights Reserved.
2218 *
2219 * Use of this source code is governed by an MIT-style license that can be
2220 * found in the LICENSE file at https://angular.io/license
2221 */
2222
2223/**
2224 * @license
2225 * Copyright Google LLC All Rights Reserved.
2226 *
2227 * Use of this source code is governed by an MIT-style license that can be
2228 * found in the LICENSE file at https://angular.io/license
2229 */
2230/**
2231* The flex API permits 3 or 1 parts of the value:
2232* - `flex-grow flex-shrink flex-basis`, or
2233* - `flex-basis`
2234*/
2235function validateBasis(basis, grow = '1', shrink = '1') {
2236 let parts = [grow, shrink, basis];
2237 let j = basis.indexOf('calc');
2238 if (j > 0) {
2239 parts[2] = _validateCalcValue(basis.substring(j).trim());
2240 let matches = basis.substr(0, j).trim().split(' ');
2241 if (matches.length == 2) {
2242 parts[0] = matches[0];
2243 parts[1] = matches[1];
2244 }
2245 }
2246 else if (j == 0) {
2247 parts[2] = _validateCalcValue(basis.trim());
2248 }
2249 else {
2250 let matches = basis.split(' ');
2251 parts = (matches.length === 3) ? matches : [
2252 grow, shrink, basis
2253 ];
2254 }
2255 return parts;
2256}
2257/**
2258 * Calc expressions require whitespace before & after any expression operators
2259 * This is a simple, crude whitespace padding solution.
2260 * - '3 3 calc(15em + 20px)'
2261 * - calc(100% / 7 * 2)
2262 * - 'calc(15em + 20px)'
2263 * - 'calc(15em+20px)'
2264 * - '37px'
2265 * = '43%'
2266 */
2267function _validateCalcValue(calc) {
2268 return calc.replace(/[\s]/g, '').replace(/[\/\*\+\-]/g, ' $& ');
2269}
2270
2271const MULTIPLIER_SUFFIX = 'x';
2272function multiply(value, multiplier) {
2273 if (multiplier === undefined) {
2274 return value;
2275 }
2276 const transformValue = (possibleValue) => {
2277 const numberValue = +(possibleValue.slice(0, -MULTIPLIER_SUFFIX.length));
2278 if (value.endsWith(MULTIPLIER_SUFFIX) && !isNaN(numberValue)) {
2279 return `${numberValue * multiplier.value}${multiplier.unit}`;
2280 }
2281 return value;
2282 };
2283 return value.includes(' ') ?
2284 value.split(' ').map(transformValue).join(' ') : transformValue(value);
2285}
2286
2287/**
2288 * @license
2289 * Copyright Google LLC All Rights Reserved.
2290 *
2291 * Use of this source code is governed by an MIT-style license that can be
2292 * found in the LICENSE file at https://angular.io/license
2293 */
2294
2295/**
2296 * Generated bundle index. Do not edit.
2297 */
2298
2299export { BREAKPOINT, BREAKPOINTS, BREAKPOINT_PRINT, BROWSER_PROVIDER, BaseDirective2, BreakPointRegistry, CLASS_NAME, CoreModule, DEFAULT_BREAKPOINTS, DEFAULT_CONFIG, LAYOUT_CONFIG, MediaChange, MediaMarshaller, MediaObserver, MediaTrigger, ORIENTATION_BREAKPOINTS, PrintHook, SERVER_TOKEN, ScreenTypes, StyleBuilder, StyleUtils, StylesheetMap, coerceArray, mergeAlias, removeStyles, sortAscendingPriority, sortDescendingPriority, validateBasis, MatchMedia as ɵMatchMedia, MockMatchMedia as ɵMockMatchMedia, MockMatchMediaProvider as ɵMockMatchMediaProvider, multiply as ɵmultiply };
2300//# sourceMappingURL=angular-flex-layout-core.mjs.map