UNPKG

@angular/flex-layout

Version:
1,636 lines (1,608 loc) 106 kB
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { APP_BOOTSTRAP_LISTENER, PLATFORM_ID, NgModule, Injectable, InjectionToken, Inject, inject, NgZone, ɵɵdefineInjectable, ɵɵinject } from '@angular/core'; import { DOCUMENT, isPlatformBrowser, isPlatformServer } from '@angular/common'; import { Subject, BehaviorSubject, Observable, merge, asapScheduler, of, fromEvent } from 'rxjs'; import { filter, debounceTime, map, switchMap, takeUntil, take, tap } from 'rxjs/operators'; /** * @fileoverview added by tsickle * Generated from: core/browser-provider.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Find all of the server-generated stylings, if any, and remove them * This will be in the form of inline classes and the style block in the * head of the DOM * @param {?} _document * @param {?} platformId * @return {?} */ function removeStyles(_document, platformId) { return (/** * @return {?} */ () => { if (isPlatformBrowser(platformId)) { /** @type {?} */ const elements = Array.from(_document.querySelectorAll(`[class*=${CLASS_NAME}]`)); // RegExp constructor should only be used if passing a variable to the constructor. // When using static regular expression it is more performant to use reg exp literal. // This is also needed to provide Safari 9 compatibility, please see // https://stackoverflow.com/questions/37919802 for more discussion. /** @type {?} */ const classRegex = /\bflex-layout-.+?\b/g; elements.forEach((/** * @param {?} el * @return {?} */ el => { el.classList.contains(`${CLASS_NAME}ssr`) && el.parentNode ? el.parentNode.removeChild(el) : el.className.replace(classRegex, ''); })); } }); } /** * Provider to remove SSR styles on the browser * @type {?} */ const BROWSER_PROVIDER = { provide: (/** @type {?} */ (APP_BOOTSTRAP_LISTENER)), useFactory: removeStyles, deps: [DOCUMENT, PLATFORM_ID], multi: true }; /** @type {?} */ const CLASS_NAME = 'flex-layout-'; /** * @fileoverview added by tsickle * Generated from: core/module.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * ***************************************************************** * Define module for common Angular Layout utilities * ***************************************************************** */ class CoreModule { } CoreModule.decorators = [ { type: NgModule, args: [{ providers: [BROWSER_PROVIDER] },] }, ]; /** * @fileoverview added by tsickle * Generated from: core/media-change.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Class instances emitted [to observers] for each mql notification */ class MediaChange { /** * @param {?=} matches whether the mediaQuery is currently activated * @param {?=} mediaQuery e.g. (min-width: 600px) and (max-width: 959px) * @param {?=} mqAlias e.g. gt-sm, md, gt-lg * @param {?=} suffix e.g. GtSM, Md, GtLg * @param {?=} priority the priority of activation for the given breakpoint */ constructor(matches = false, mediaQuery = 'all', mqAlias = '', suffix = '', priority = 0) { this.matches = matches; this.mediaQuery = mediaQuery; this.mqAlias = mqAlias; this.suffix = suffix; this.priority = priority; this.property = ''; } /** * Create an exact copy of the MediaChange * @return {?} */ clone() { return new MediaChange(this.matches, this.mediaQuery, this.mqAlias, this.suffix); } } /** * @fileoverview added by tsickle * Generated from: core/stylesheet-map/stylesheet-map.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Utility to emulate a CSS stylesheet * * This utility class stores all of the styles for a given HTML element * as a readonly `stylesheet` map. */ class StylesheetMap { constructor() { this.stylesheet = new Map(); } /** * Add an individual style to an HTML element * @param {?} element * @param {?} style * @param {?} value * @return {?} */ addStyleToElement(element, style, value) { /** @type {?} */ const stylesheet = this.stylesheet.get(element); if (stylesheet) { stylesheet.set(style, value); } else { this.stylesheet.set(element, new Map([[style, value]])); } } /** * Clear the virtual stylesheet * @return {?} */ clearStyles() { this.stylesheet.clear(); } /** * Retrieve a given style for an HTML element * @param {?} el * @param {?} styleName * @return {?} */ getStyleForElement(el, styleName) { /** @type {?} */ const styles = this.stylesheet.get(el); /** @type {?} */ let value = ''; if (styles) { /** @type {?} */ const style = styles.get(styleName); if (typeof style === 'number' || typeof style === 'string') { value = style + ''; } } return value; } } StylesheetMap.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ StylesheetMap.ɵprov = ɵɵdefineInjectable({ factory: function StylesheetMap_Factory() { return new StylesheetMap(); }, token: StylesheetMap, providedIn: "root" }); /** * @fileoverview added by tsickle * Generated from: core/stylesheet-map/index.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: core/tokens/library-config.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const DEFAULT_CONFIG = { addFlexToParent: true, addOrientationBps: false, disableDefaultBps: false, disableVendorPrefixes: false, serverLoaded: false, useColumnBasisZero: true, printWithBreakpoints: [], mediaTriggerAutoRestore: true, ssrObserveBreakpoints: [], }; /** @type {?} */ const LAYOUT_CONFIG = new InjectionToken('Flex Layout token, config options for the library', { providedIn: 'root', factory: (/** * @return {?} */ () => DEFAULT_CONFIG) }); /** * @fileoverview added by tsickle * Generated from: core/tokens/server-token.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Token that is provided to tell whether the FlexLayoutServerModule * has been included in the bundle * * NOTE: This can be manually provided to disable styles when using SSR * @type {?} */ const SERVER_TOKEN = new InjectionToken('FlexLayoutServerLoaded', { providedIn: 'root', factory: (/** * @return {?} */ () => false) }); /** * @fileoverview added by tsickle * Generated from: core/tokens/breakpoint-token.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const BREAKPOINT = new InjectionToken('Flex Layout token, collect all breakpoints into one provider', { providedIn: 'root', factory: (/** * @return {?} */ () => null) }); /** * @fileoverview added by tsickle * Generated from: core/tokens/index.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: core/add-alias.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * For the specified MediaChange, make sure it contains the breakpoint alias * and suffix (if available). * @param {?} dest * @param {?} source * @return {?} */ function mergeAlias(dest, source) { dest = dest ? dest.clone() : new MediaChange(); if (source) { dest.mqAlias = source.alias; dest.mediaQuery = source.mediaQuery; dest.suffix = (/** @type {?} */ (source.suffix)); dest.priority = (/** @type {?} */ (source.priority)); } return dest; } /** * @fileoverview added by tsickle * Generated from: utils/layout-validator.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license * @type {?} */ const INLINE = 'inline'; /** @type {?} */ const LAYOUT_VALUES = ['row', 'column', 'row-reverse', 'column-reverse']; /** * Validate the direction|'direction wrap' value and then update the host's inline flexbox styles * @param {?} value * @return {?} */ function buildLayoutCSS(value) { let [direction, wrap, isInline] = validateValue(value); return buildCSS(direction, wrap, isInline); } /** * Validate the value to be one of the acceptable value options * Use default fallback of 'row' * @param {?} value * @return {?} */ function validateValue(value) { value = value ? value.toLowerCase() : ''; let [direction, wrap, inline] = value.split(' '); // First value must be the `flex-direction` if (!LAYOUT_VALUES.find((/** * @param {?} x * @return {?} */ x => x === direction))) { direction = LAYOUT_VALUES[0]; } if (wrap === INLINE) { wrap = (inline !== INLINE) ? inline : ''; inline = INLINE; } return [direction, validateWrapValue(wrap), !!inline]; } /** * Convert layout-wrap='<value>' to expected flex-wrap style * @param {?} value * @return {?} */ function validateWrapValue(value) { if (!!value) { switch (value.toLowerCase()) { case 'reverse': case 'wrap-reverse': case 'reverse-wrap': value = 'wrap-reverse'; break; case 'no': case 'none': case 'nowrap': value = 'nowrap'; break; // All other values fallback to 'wrap' default: value = 'wrap'; break; } } return value; } /** * Build the CSS that should be assigned to the element instance * BUG: * 1) min-height on a column flex container won’t apply to its flex item children in IE 10-11. * Use height instead if possible; height : <xxx>vh; * * This way any padding or border specified on the child elements are * laid out and drawn inside that element's specified width and height. * @param {?} direction * @param {?=} wrap * @param {?=} inline * @return {?} */ function buildCSS(direction, wrap = null, inline = false) { return { 'display': inline ? 'inline-flex' : 'flex', 'box-sizing': 'border-box', 'flex-direction': direction, 'flex-wrap': !!wrap ? wrap : null }; } /** * @fileoverview added by tsickle * Generated from: core/base/base2.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @abstract */ class BaseDirective2 { /** * @protected * @param {?} elementRef * @param {?} styleBuilder * @param {?} styler * @param {?} marshal */ constructor(elementRef, styleBuilder, styler, marshal) { this.elementRef = elementRef; this.styleBuilder = styleBuilder; this.styler = styler; this.marshal = marshal; this.DIRECTIVE_KEY = ''; this.inputs = []; /** * The most recently used styles for the builder */ this.mru = {}; this.destroySubject = new Subject(); /** * Cache map for style computation */ this.styleCache = new Map(); } /** * Access to host element's parent DOM node * @protected * @return {?} */ get parentElement() { return this.elementRef.nativeElement.parentElement; } /** * Access to the HTMLElement for the directive * @protected * @return {?} */ get nativeElement() { return this.elementRef.nativeElement; } /** * Access to the activated value for the directive * @return {?} */ get activatedValue() { return this.marshal.getValue(this.nativeElement, this.DIRECTIVE_KEY); } /** * @param {?} value * @return {?} */ set activatedValue(value) { this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, value, this.marshal.activatedAlias); } /** * For \@Input changes * @param {?} changes * @return {?} */ ngOnChanges(changes) { Object.keys(changes).forEach((/** * @param {?} key * @return {?} */ key => { if (this.inputs.indexOf(key) !== -1) { /** @type {?} */ const bp = key.split('.').slice(1).join('.'); /** @type {?} */ const val = changes[key].currentValue; this.setValue(val, bp); } })); } /** * @return {?} */ ngOnDestroy() { this.destroySubject.next(); this.destroySubject.complete(); this.marshal.releaseElement(this.nativeElement); } /** * Register with central marshaller service * @protected * @param {?=} extraTriggers * @return {?} */ init(extraTriggers = []) { this.marshal.init(this.elementRef.nativeElement, this.DIRECTIVE_KEY, this.updateWithValue.bind(this), this.clearStyles.bind(this), extraTriggers); } /** * Add styles to the element using predefined style builder * @protected * @param {?} input * @param {?=} parent * @return {?} */ addStyles(input, parent) { /** @type {?} */ const builder = this.styleBuilder; /** @type {?} */ const useCache = builder.shouldCache; /** @type {?} */ let genStyles = this.styleCache.get(input); if (!genStyles || !useCache) { genStyles = builder.buildStyles(input, parent); if (useCache) { this.styleCache.set(input, genStyles); } } this.mru = Object.assign({}, genStyles); this.applyStyleToElement(genStyles); builder.sideEffect(input, genStyles, parent); } /** * Remove generated styles from an element using predefined style builder * @protected * @return {?} */ clearStyles() { Object.keys(this.mru).forEach((/** * @param {?} k * @return {?} */ k => { this.mru[k] = ''; })); this.applyStyleToElement(this.mru); this.mru = {}; } /** * Force trigger style updates on DOM element * @protected * @return {?} */ triggerUpdate() { this.marshal.triggerUpdate(this.nativeElement, this.DIRECTIVE_KEY); } /** * Determine the DOM element's Flexbox flow (flex-direction). * * Check inline style first then check computed (stylesheet) style. * And optionally add the flow value to element's inline style. * @protected * @param {?} target * @param {?=} addIfMissing * @return {?} */ getFlexFlowDirection(target, addIfMissing = false) { if (target) { const [value, hasInlineValue] = this.styler.getFlowDirection(target); if (!hasInlineValue && addIfMissing) { /** @type {?} */ const style = buildLayoutCSS(value); /** @type {?} */ const elements = [target]; this.styler.applyStyleToElements(style, elements); } return value.trim(); } return 'row'; } /** * Applies styles given via string pair or object map to the directive element * @protected * @param {?} style * @param {?=} value * @param {?=} element * @return {?} */ applyStyleToElement(style, value, element = this.nativeElement) { this.styler.applyStyleToElement(element, style, value); } /** * @protected * @param {?} val * @param {?} bp * @return {?} */ setValue(val, bp) { this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, val, bp); } /** * @protected * @param {?} input * @return {?} */ updateWithValue(input) { this.addStyles(input); } } /** * @fileoverview added by tsickle * Generated from: core/base/index.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: core/breakpoints/data/break-points.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * NOTE: Smaller ranges have HIGHER priority since the match is more specific * @type {?} */ const DEFAULT_BREAKPOINTS = [ { alias: 'xs', mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.9px)', priority: 1000, }, { alias: 'sm', mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.9px)', priority: 900, }, { alias: 'md', mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.9px)', priority: 800, }, { alias: 'lg', mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.9px)', priority: 700, }, { alias: 'xl', mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.9px)', priority: 600, }, { alias: 'lt-sm', overlapping: true, mediaQuery: 'screen and (max-width: 599.9px)', priority: 950, }, { alias: 'lt-md', overlapping: true, mediaQuery: 'screen and (max-width: 959.9px)', priority: 850, }, { alias: 'lt-lg', overlapping: true, mediaQuery: 'screen and (max-width: 1279.9px)', priority: 750, }, { alias: 'lt-xl', overlapping: true, priority: 650, mediaQuery: 'screen and (max-width: 1919.9px)', }, { alias: 'gt-xs', overlapping: true, mediaQuery: 'screen and (min-width: 600px)', priority: -950, }, { alias: 'gt-sm', overlapping: true, mediaQuery: 'screen and (min-width: 960px)', priority: -850, }, { alias: 'gt-md', overlapping: true, mediaQuery: 'screen and (min-width: 1280px)', priority: -750, }, { alias: 'gt-lg', overlapping: true, mediaQuery: 'screen and (min-width: 1920px)', priority: -650, } ]; /** * @fileoverview added by tsickle * Generated from: core/breakpoints/data/orientation-break-points.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /* tslint:disable */ /** @type {?} */ const HANDSET_PORTRAIT = '(orientation: portrait) and (max-width: 599.9px)'; /** @type {?} */ const HANDSET_LANDSCAPE = '(orientation: landscape) and (max-width: 959.9px)'; /** @type {?} */ const TABLET_PORTRAIT = '(orientation: portrait) and (min-width: 600px) and (max-width: 839.9px)'; /** @type {?} */ const TABLET_LANDSCAPE = '(orientation: landscape) and (min-width: 960px) and (max-width: 1279.9px)'; /** @type {?} */ const WEB_PORTRAIT = '(orientation: portrait) and (min-width: 840px)'; /** @type {?} */ const WEB_LANDSCAPE = '(orientation: landscape) and (min-width: 1280px)'; /** @type {?} */ const ScreenTypes = { 'HANDSET': `${HANDSET_PORTRAIT}, ${HANDSET_LANDSCAPE}`, 'TABLET': `${TABLET_PORTRAIT} , ${TABLET_LANDSCAPE}`, 'WEB': `${WEB_PORTRAIT}, ${WEB_LANDSCAPE} `, 'HANDSET_PORTRAIT': `${HANDSET_PORTRAIT}`, 'TABLET_PORTRAIT': `${TABLET_PORTRAIT} `, 'WEB_PORTRAIT': `${WEB_PORTRAIT}`, 'HANDSET_LANDSCAPE': `${HANDSET_LANDSCAPE}]`, 'TABLET_LANDSCAPE': `${TABLET_LANDSCAPE}`, 'WEB_LANDSCAPE': `${WEB_LANDSCAPE}` }; /** * Extended Breakpoints for handset/tablets with landscape or portrait orientations * @type {?} */ const ORIENTATION_BREAKPOINTS = [ { 'alias': 'handset', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET }, { 'alias': 'handset.landscape', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_LANDSCAPE }, { 'alias': 'handset.portrait', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_PORTRAIT }, { 'alias': 'tablet', priority: 2100, 'mediaQuery': ScreenTypes.TABLET }, { 'alias': 'tablet.landscape', priority: 2100, 'mediaQuery': ScreenTypes.TABLET }, { 'alias': 'tablet.portrait', priority: 2100, 'mediaQuery': ScreenTypes.TABLET_PORTRAIT }, { 'alias': 'web', priority: 2200, 'mediaQuery': ScreenTypes.WEB, overlapping: true }, { 'alias': 'web.landscape', priority: 2200, 'mediaQuery': ScreenTypes.WEB_LANDSCAPE, overlapping: true }, { 'alias': 'web.portrait', priority: 2200, 'mediaQuery': ScreenTypes.WEB_PORTRAIT, overlapping: true } ]; /** * @fileoverview added by tsickle * Generated from: core/breakpoints/break-point.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: utils/object-extend.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Extends an object with the *enumerable* and *own* properties of one or more source objects, * similar to Object.assign. * * @param {?} dest The object which will have properties copied to it. * @param {...?} sources The source objects from which properties will be copied. * @return {?} */ function extendObject(dest, ...sources) { if (dest == null) { throw TypeError('Cannot convert undefined or null to object'); } for (let source of sources) { if (source != null) { for (let key in source) { if (source.hasOwnProperty(key)) { dest[key] = source[key]; } } } } return dest; } /** * @fileoverview added by tsickle * Generated from: core/breakpoints/breakpoint-tools.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const ALIAS_DELIMITERS = /(\.|-|_)/g; /** * @param {?} part * @return {?} */ function firstUpperCase(part) { /** @type {?} */ let first = part.length > 0 ? part.charAt(0) : ''; /** @type {?} */ let remainder = (part.length > 1) ? part.slice(1) : ''; return first.toUpperCase() + remainder; } /** * Converts snake-case to SnakeCase. * @param {?} name Text to UpperCamelCase * @return {?} */ function camelCase(name) { return name .replace(ALIAS_DELIMITERS, '|') .split('|') .map(firstUpperCase) .join(''); } /** * For each breakpoint, ensure that a Suffix is defined; * fallback to UpperCamelCase the unique Alias value * @param {?} list * @return {?} */ function validateSuffixes(list) { list.forEach((/** * @param {?} bp * @return {?} */ (bp) => { if (!bp.suffix) { bp.suffix = camelCase(bp.alias); // create Suffix value based on alias bp.overlapping = !!bp.overlapping; // ensure default value } })); return list; } /** * Merge a custom breakpoint list with the default list based on unique alias values * - Items are added if the alias is not in the default list * - Items are merged with the custom override if the alias exists in the default list * @param {?} defaults * @param {?=} custom * @return {?} */ function mergeByAlias(defaults, custom = []) { /** @type {?} */ const dict = {}; defaults.forEach((/** * @param {?} bp * @return {?} */ bp => { dict[bp.alias] = bp; })); // Merge custom breakpoints custom.forEach((/** * @param {?} bp * @return {?} */ (bp) => { if (dict[bp.alias]) { extendObject(dict[bp.alias], bp); } else { dict[bp.alias] = bp; } })); return validateSuffixes(Object.keys(dict).map((/** * @param {?} k * @return {?} */ k => dict[k]))); } /** * @fileoverview added by tsickle * Generated from: core/breakpoints/break-points-token.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Injection token unique to the flex-layout library. * Use this token when build a custom provider (see below). * @type {?} */ const BREAKPOINTS = new InjectionToken('Token (@angular/flex-layout) Breakpoints', { providedIn: 'root', factory: (/** * @return {?} */ () => { /** @type {?} */ const breakpoints = inject(BREAKPOINT); /** @type {?} */ const layoutConfig = inject(LAYOUT_CONFIG); /** @type {?} */ const bpFlattenArray = [].concat.apply([], (breakpoints || []) .map((/** * @param {?} v * @return {?} */ (v) => Array.isArray(v) ? v : [v]))); /** @type {?} */ const builtIns = (layoutConfig.disableDefaultBps ? [] : DEFAULT_BREAKPOINTS) .concat(layoutConfig.addOrientationBps ? ORIENTATION_BREAKPOINTS : []); return mergeByAlias(builtIns, bpFlattenArray); }) }); /** * @fileoverview added by tsickle * Generated from: core/utils/sort.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * HOF to sort the breakpoints by descending priority * @template T * @param {?} a * @param {?} b * @return {?} */ function sortDescendingPriority(a, b) { /** @type {?} */ const priorityA = a ? a.priority || 0 : 0; /** @type {?} */ const priorityB = b ? b.priority || 0 : 0; return priorityB - priorityA; } /** * HOF to sort the breakpoints by ascending priority * @template T * @param {?} a * @param {?} b * @return {?} */ function sortAscendingPriority(a, b) { /** @type {?} */ const pA = a.priority || 0; /** @type {?} */ const pB = b.priority || 0; return pA - pB; } /** * @fileoverview added by tsickle * Generated from: core/breakpoints/break-point-registry.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Registry of 1..n MediaQuery breakpoint ranges * This is published as a provider and may be overridden from custom, application-specific ranges * */ class BreakPointRegistry { /** * @param {?} list */ constructor(list) { /** * Memoized BreakPoint Lookups */ this.findByMap = new Map(); this.items = [...list].sort(sortAscendingPriority); } /** * Search breakpoints by alias (e.g. gt-xs) * @param {?} alias * @return {?} */ findByAlias(alias) { return !alias ? null : this.findWithPredicate(alias, (/** * @param {?} bp * @return {?} */ (bp) => bp.alias == alias)); } /** * @param {?} query * @return {?} */ findByQuery(query) { return this.findWithPredicate(query, (/** * @param {?} bp * @return {?} */ (bp) => bp.mediaQuery == query)); } /** * Get all the breakpoints whose ranges could overlapping `normal` ranges; * e.g. gt-sm overlaps md, lg, and xl * @return {?} */ get overlappings() { return this.items.filter((/** * @param {?} it * @return {?} */ it => it.overlapping == true)); } /** * Get list of all registered (non-empty) breakpoint aliases * @return {?} */ get aliases() { return this.items.map((/** * @param {?} it * @return {?} */ it => it.alias)); } /** * Aliases are mapped to properties using suffixes * e.g. 'gt-sm' for property 'layout' uses suffix 'GtSm' * for property layoutGtSM. * @return {?} */ get suffixes() { return this.items.map((/** * @param {?} it * @return {?} */ it => !!it.suffix ? it.suffix : '')); } /** * Memoized lookup using custom predicate function * @private * @param {?} key * @param {?} searchFn * @return {?} */ findWithPredicate(key, searchFn) { /** @type {?} */ let response = this.findByMap.get(key); if (!response) { response = this.items.find(searchFn) || null; this.findByMap.set(key, response); } return response || null; } } BreakPointRegistry.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ BreakPointRegistry.ctorParameters = () => [ { type: Array, decorators: [{ type: Inject, args: [BREAKPOINTS,] }] } ]; /** @nocollapse */ BreakPointRegistry.ɵprov = ɵɵdefineInjectable({ factory: function BreakPointRegistry_Factory() { return new BreakPointRegistry(ɵɵinject(BREAKPOINTS)); }, token: BreakPointRegistry, providedIn: "root" }); /** * @fileoverview added by tsickle * Generated from: core/breakpoints/index.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: core/match-media/match-media.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * MediaMonitor configures listeners to mediaQuery changes and publishes an Observable facade to * convert mediaQuery change callbacks to subscriber notifications. These notifications will be * performed within the ng Zone to trigger change detections and component updates. * * NOTE: both mediaQuery activations and de-activations are announced in notifications */ class MatchMedia { /** * @param {?} _zone * @param {?} _platformId * @param {?} _document */ constructor(_zone, _platformId, _document) { this._zone = _zone; this._platformId = _platformId; this._document = _document; /** * Initialize source with 'all' so all non-responsive APIs trigger style updates */ this.source = new BehaviorSubject(new MediaChange(true)); this.registry = new Map(); this._observable$ = this.source.asObservable(); } /** * Publish list of all current activations * @return {?} */ get activations() { /** @type {?} */ const results = []; this.registry.forEach((/** * @param {?} mql * @param {?} key * @return {?} */ (mql, key) => { if (mql.matches) { results.push(key); } })); return results; } /** * For the specified mediaQuery? * @param {?} mediaQuery * @return {?} */ isActive(mediaQuery) { /** @type {?} */ const mql = this.registry.get(mediaQuery); return !!mql ? mql.matches : false; } /** * External observers can watch for all (or a specific) mql changes. * Typically used by the MediaQueryAdaptor; optionally available to components * who wish to use the MediaMonitor as mediaMonitor$ observable service. * * Use deferred registration process to register breakpoints only on subscription * This logic also enforces logic to register all mediaQueries BEFORE notify * subscribers of notifications. * @param {?=} mqList * @param {?=} filterOthers * @return {?} */ observe(mqList, filterOthers = false) { if (mqList && mqList.length) { /** @type {?} */ const matchMedia$ = this._observable$.pipe(filter((/** * @param {?} change * @return {?} */ (change) => { return !filterOthers ? true : (mqList.indexOf(change.mediaQuery) > -1); }))); /** @type {?} */ const registration$ = new Observable((/** * @param {?} observer * @return {?} */ (observer) => { // tslint:disable-line:max-line-length /** @type {?} */ const matches = this.registerQuery(mqList); if (matches.length) { /** @type {?} */ const lastChange = (/** @type {?} */ (matches.pop())); matches.forEach((/** * @param {?} e * @return {?} */ (e) => { observer.next(e); })); this.source.next(lastChange); // last match is cached } observer.complete(); })); return merge(registration$, matchMedia$); } return this._observable$; } /** * Based on the BreakPointRegistry provider, register internal listeners for each unique * mediaQuery. Each listener emits specific MediaChange data to observers * @param {?} mediaQuery * @return {?} */ registerQuery(mediaQuery) { /** @type {?} */ const list = Array.isArray(mediaQuery) ? mediaQuery : [mediaQuery]; /** @type {?} */ const matches = []; buildQueryCss(list, this._document); list.forEach((/** * @param {?} query * @return {?} */ (query) => { /** @type {?} */ const onMQLEvent = (/** * @param {?} e * @return {?} */ (e) => { this._zone.run((/** * @return {?} */ () => this.source.next(new MediaChange(e.matches, query)))); }); /** @type {?} */ let mql = this.registry.get(query); if (!mql) { mql = this.buildMQL(query); mql.addListener(onMQLEvent); this.registry.set(query, mql); } if (mql.matches) { matches.push(new MediaChange(true, query)); } })); return matches; } /** * Call window.matchMedia() to build a MediaQueryList; which * supports 0..n listeners for activation/deactivation * @protected * @param {?} query * @return {?} */ buildMQL(query) { return constructMql(query, isPlatformBrowser(this._platformId)); } } MatchMedia.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ MatchMedia.ctorParameters = () => [ { type: NgZone }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; /** @nocollapse */ MatchMedia.ɵprov = ɵɵdefineInjectable({ factory: function MatchMedia_Factory() { return new MatchMedia(ɵɵinject(NgZone), ɵɵinject(PLATFORM_ID), ɵɵinject(DOCUMENT)); }, token: MatchMedia, providedIn: "root" }); /** * Private global registry for all dynamically-created, injected style tags * @see prepare(query) * @type {?} */ const ALL_STYLES = {}; /** * For Webkit engines that only trigger the MediaQueryList Listener * when there is at least one CSS selector for the respective media query. * * @param {?} mediaQueries * @param {?} _document * @return {?} */ function buildQueryCss(mediaQueries, _document) { /** @type {?} */ const list = mediaQueries.filter((/** * @param {?} it * @return {?} */ it => !ALL_STYLES[it])); if (list.length > 0) { /** @type {?} */ const query = list.join(', '); try { /** @type {?} */ const styleEl = _document.createElement('style'); styleEl.setAttribute('type', 'text/css'); if (!((/** @type {?} */ (styleEl))).styleSheet) { /** @type {?} */ const cssText = ` /* @angular/flex-layout - workaround for possible browser quirk with mediaQuery listeners see http://bit.ly/2sd4HMP */ @media ${query} {.fx-query-test{ }} `; styleEl.appendChild(_document.createTextNode(cssText)); } (/** @type {?} */ (_document.head)).appendChild(styleEl); // Store in private global registry list.forEach((/** * @param {?} mq * @return {?} */ mq => ALL_STYLES[mq] = styleEl)); } catch (e) { console.error(e); } } } /** * @param {?} query * @param {?} isBrowser * @return {?} */ function constructMql(query, isBrowser) { /** @type {?} */ const canListen = isBrowser && !!((/** @type {?} */ (window))).matchMedia('all').addListener; return canListen ? ((/** @type {?} */ (window))).matchMedia(query) : (/** @type {?} */ ((/** @type {?} */ ({ matches: query === 'all' || query === '', media: query, addListener: (/** * @return {?} */ () => { }), removeListener: (/** * @return {?} */ () => { }) })))); } /** * @fileoverview added by tsickle * Generated from: core/match-media/mock/mock-match-media.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * MockMatchMedia mocks calls to the Window API matchMedia with a build of a simulated * MockMediaQueryListener. Methods are available to simulate an activation of a mediaQuery * range and to clearAll mediaQuery listeners. */ class MockMatchMedia extends MatchMedia { // Allow fallback to overlapping mediaQueries /** * @param {?} _zone * @param {?} _platformId * @param {?} _document * @param {?} _breakpoints */ constructor(_zone, _platformId, _document, _breakpoints) { super(_zone, _platformId, _document); this._breakpoints = _breakpoints; this.autoRegisterQueries = true; // Used for testing BreakPoint registrations // Used for testing BreakPoint registrations this.useOverlaps = false; // Allow fallback to overlapping mediaQueries } /** * Easy method to clear all listeners for all mediaQueries * @return {?} */ clearAll() { this.registry.forEach((/** * @param {?} mql * @return {?} */ (mql) => { ((/** @type {?} */ (mql))).destroy(); })); this.registry.clear(); this.useOverlaps = false; } /** * Feature to support manual, simulated activation of a mediaQuery. * @param {?} mediaQuery * @param {?=} useOverlaps * @return {?} */ activate(mediaQuery, useOverlaps = false) { useOverlaps = useOverlaps || this.useOverlaps; mediaQuery = this._validateQuery(mediaQuery); if (useOverlaps || !this.isActive(mediaQuery)) { this._deactivateAll(); this._registerMediaQuery(mediaQuery); this._activateWithOverlaps(mediaQuery, useOverlaps); } return this.hasActivated; } /** * Converts an optional mediaQuery alias to a specific, valid mediaQuery * @param {?} queryOrAlias * @return {?} */ _validateQuery(queryOrAlias) { /** @type {?} */ const bp = this._breakpoints.findByAlias(queryOrAlias); return (bp && bp.mediaQuery) || queryOrAlias; } /** * Manually onMediaChange any overlapping mediaQueries to simulate * similar functionality in the window.matchMedia() * @private * @param {?} mediaQuery * @param {?} useOverlaps * @return {?} */ _activateWithOverlaps(mediaQuery, useOverlaps) { if (useOverlaps) { /** @type {?} */ const bp = this._breakpoints.findByQuery(mediaQuery); /** @type {?} */ const alias = bp ? bp.alias : 'unknown'; // Simulate activation of overlapping lt-<XXX> ranges switch (alias) { case 'lg': this._activateByAlias('lt-xl'); break; case 'md': this._activateByAlias('lt-xl, lt-lg'); break; case 'sm': this._activateByAlias('lt-xl, lt-lg, lt-md'); break; case 'xs': this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm'); break; } // Simulate activation of overlapping gt-<xxxx> mediaQuery ranges switch (alias) { case 'xl': this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs'); break; case 'lg': this._activateByAlias('gt-md, gt-sm, gt-xs'); break; case 'md': this._activateByAlias('gt-sm, gt-xs'); break; case 'sm': this._activateByAlias('gt-xs'); break; } } // Activate last since the responsiveActivation is watching *this* mediaQuery return this._activateByQuery(mediaQuery); } /** * * @private * @param {?} aliases * @return {?} */ _activateByAlias(aliases) { /** @type {?} */ const activate = (/** * @param {?} alias * @return {?} */ (alias) => { /** @type {?} */ const bp = this._breakpoints.findByAlias(alias); this._activateByQuery(bp ? bp.mediaQuery : alias); }); aliases.split(',').forEach((/** * @param {?} alias * @return {?} */ alias => activate(alias.trim()))); } /** * * @private * @param {?} mediaQuery * @return {?} */ _activateByQuery(mediaQuery) { /** @type {?} */ const mql = (/** @type {?} */ (this.registry.get(mediaQuery))); if (mql && !this.isActive(mediaQuery)) { this.registry.set(mediaQuery, mql.activate()); } return this.hasActivated; } /** * Deactivate all current MQLs and reset the buffer * @private * @template THIS * @this {THIS} * @return {THIS} */ _deactivateAll() { (/** @type {?} */ (this)).registry.forEach((/** * @param {?} it * @return {?} */ (it) => { ((/** @type {?} */ (it))).deactivate(); })); return (/** @type {?} */ (this)); } /** * Insure the mediaQuery is registered with MatchMedia * @private * @param {?} mediaQuery * @return {?} */ _registerMediaQuery(mediaQuery) { if (!this.registry.has(mediaQuery) && this.autoRegisterQueries) { this.registerQuery(mediaQuery); } } /** * Call window.matchMedia() to build a MediaQueryList; which * supports 0..n listeners for activation/deactivation * @protected * @param {?} query * @return {?} */ buildMQL(query) { return new MockMediaQueryList(query); } /** * @protected * @return {?} */ get hasActivated() { return this.activations.length > 0; } } MockMatchMedia.decorators = [ { type: Injectable }, ]; /** @nocollapse */ MockMatchMedia.ctorParameters = () => [ { type: NgZone }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: BreakPointRegistry } ]; /** * Special internal class to simulate a MediaQueryList and * - supports manual activation to simulate mediaQuery matching * - manages listeners */ class MockMediaQueryList { /** * @param {?} _mediaQuery */ constructor(_mediaQuery) { this._mediaQuery = _mediaQuery; this._isActive = false; this._listeners = []; this.onchange = null; } /** * @return {?} */ get matches() { return this._isActive; } /** * @return {?} */ get media() { return this._mediaQuery; } /** * Destroy the current list by deactivating the * listeners and clearing the internal list * @return {?} */ destroy() { this.deactivate(); this._listeners = []; } /** * Notify all listeners that 'matches === TRUE' * @return {?} */ activate() { if (!this._isActive) { this._isActive = true; this._listeners.forEach((/** * @param {?} callback * @return {?} */ (callback) => { /** @type {?} */ const cb = (/** @type {?} */ (callback)); cb.call(null, this); })); } return this; } /** * Notify all listeners that 'matches === false' * @return {?} */ deactivate() { if (this._isActive) { this._isActive = false; this._listeners.forEach((/** * @param {?} callback * @return {?} */ (callback) => { /** @type {?} */ const cb = (/** @type {?} */ (callback)); cb.call(null, this); })); } return this; } /** * Add a listener to our internal list to activate later * @param {?} listener * @return {?} */ addListener(listener) { if (this._listeners.indexOf(listener) === -1) { this._listeners.push(listener); } if (this._isActive) { /** @type {?} */ const cb = (/** @type {?} */ (listener)); cb.call(null, this); } } /** * Don't need to remove listeners in the testing environment * @param {?} _ * @return {?} */ removeListener(_) { } /** * @param {?} _ * @param {?} __ * @param {?=} ___ * @return {?} */ addEventListener(_, __, ___) { } /** * @param {?} _ * @param {?} __ * @param {?=} ___ * @return {?} */ removeEventListener(_, __, ___) { } /** * @param {?} _ * @return {?} */ dispatchEvent(_) { return false; } } /** * Pre-configured provider for MockMatchMedia * @type {?} */ const MockMatchMediaProvider = { // tslint:disable-line:variable-name provide: MatchMedia, useClass: MockMatchMedia }; /** * @fileoverview added by tsickle * Generated from: core/match-media/index.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: core/media-marshaller/print-hook.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const PRINT = 'print'; /** @type {?} */ const BREAKPOINT_PRINT = { alias: PRINT, mediaQuery: PRINT, priority: 1000 }; /** * PrintHook - Use to intercept print MediaQuery activations and force * layouts to render with the specified print alias/breakpoint * * Used in MediaMarshaller and MediaObserver */ class PrintHook { /** * @param {?} breakpoints * @param {?} layoutConfig * @param {?} _document */ constructor(breakpoints, layoutConfig, _document) { this.breakpoints = breakpoints; this.layoutConfig = layoutConfig; this._document = _document; // registeredBeforeAfterPrintHooks tracks if we registered the `beforeprint` // and `afterprint` event listeners. this.registeredBeforeAfterPrintHooks = false; // isPrintingBeforeAfterEvent is used to track if we are printing from within // a `beforeprint` event handler. This prevents the typicall `stopPrinting` // form `interceptEvents` so that printing is not stopped while the dialog // is still open. This is an extension of the `isPrinting` property