@ngbracket/ngx-layout
Version:
ngbracket/ngx-layout =======
1 lines • 145 kB
Source Map (JSON)
{"version":3,"file":"ngbracket-ngx-layout-core.mjs","sources":["../../../../projects/libs/flex-layout/core/media-change.ts","../../../../projects/libs/flex-layout/core/add-alias.ts","../../../../projects/libs/flex-layout/core/browser-provider.ts","../../../../projects/libs/flex-layout/core/module.ts","../../../../projects/libs/flex-layout/core/stylesheet-map/stylesheet-map.ts","../../../../projects/libs/flex-layout/core/tokens/breakpoint-token.ts","../../../../projects/libs/flex-layout/core/tokens/library-config.ts","../../../../projects/libs/flex-layout/core/tokens/server-token.ts","../../../../projects/libs/flex-layout/core/utils/sort.ts","../../../../projects/libs/flex-layout/core/breakpoints/breakpoint-tools.ts","../../../../projects/libs/flex-layout/core/breakpoints/data/break-points.ts","../../../../projects/libs/flex-layout/core/breakpoints/data/orientation-break-points.ts","../../../../projects/libs/flex-layout/core/breakpoints/break-points-token.ts","../../../../projects/libs/flex-layout/core/breakpoints/break-point-registry.ts","../../../../projects/libs/flex-layout/core/match-media/match-media.ts","../../../../projects/libs/flex-layout/core/media-marshaller/print-hook.ts","../../../../projects/libs/flex-layout/core/media-marshaller/media-marshaller.ts","../../../../projects/libs/flex-layout/core/style-builder/style-builder.ts","../../../../projects/libs/flex-layout/core/style-utils/style-utils.ts","../../../../projects/libs/flex-layout/core/base/base2.ts","../../../../projects/libs/flex-layout/core/breakpoints/data/material-break-points.ts","../../../../projects/libs/flex-layout/core/match-media/mock/mock-match-media.ts","../../../../projects/libs/flex-layout/core/utils/array.ts","../../../../projects/libs/flex-layout/core/media-observer/media-observer.ts","../../../../projects/libs/flex-layout/core/media-trigger/media-trigger.ts","../../../../projects/libs/flex-layout/core/basis-validator/basis-validator.ts","../../../../projects/libs/flex-layout/core/multiply/multiplier.ts","../../../../projects/libs/flex-layout/core/ngbracket-ngx-layout-core.ts"],"sourcesContent":["export type MediaQuerySubscriber = (changes: MediaChange) => void;\n\n/**\n * Class instances emitted [to observers] for each mql notification\n */\nexport class MediaChange {\n property: string = '';\n value: any;\n\n /**\n * @param matches whether the mediaQuery is currently activated\n * @param mediaQuery e.g. (min-width: 600px) and (max-width: 959px)\n * @param mqAlias e.g. gt-sm, md, gt-lg\n * @param suffix e.g. GtSM, Md, GtLg\n * @param priority the priority of activation for the given breakpoint\n */\n constructor(\n public matches = false,\n public mediaQuery = 'all',\n public mqAlias = '',\n public suffix = '',\n public priority = 0,\n ) {}\n\n /** Create an exact copy of the MediaChange */\n clone(): MediaChange {\n return new MediaChange(\n this.matches,\n this.mediaQuery,\n this.mqAlias,\n this.suffix,\n );\n }\n}\n","import { OptionalBreakPoint } from './breakpoints';\nimport { MediaChange } from './media-change';\n\n/**\n * For the specified MediaChange, make sure it contains the breakpoint alias\n * and suffix (if available).\n */\nexport function mergeAlias(\n dest: MediaChange,\n source: OptionalBreakPoint,\n): MediaChange {\n dest = dest?.clone() ?? new MediaChange();\n if (source) {\n dest.mqAlias = source.alias;\n dest.mediaQuery = source.mediaQuery;\n dest.suffix = source.suffix as string;\n dest.priority = source.priority as number;\n }\n return dest;\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n APP_BOOTSTRAP_LISTENER,\n InjectionToken,\n PLATFORM_ID,\n DOCUMENT\n} from '@angular/core';\n\n/**\n * Find all of the server-generated stylings, if any, and remove them\n * This will be in the form of inline classes and the style block in the\n * head of the DOM\n */\nexport function removeStyles(_document: Document, platformId: Object) {\n return () => {\n if (isPlatformBrowser(platformId)) {\n const elements = Array.from(\n _document.querySelectorAll(`[class*=${CLASS_NAME}]`),\n );\n\n // RegExp constructor should only be used if passing a variable to the constructor.\n // When using static regular expression it is more performant to use reg exp literal.\n // This is also needed to provide Safari 9 compatibility, please see\n // https://stackoverflow.com/questions/37919802 for more discussion.\n const classRegex = /\\bflex-layout-.+?\\b/g;\n elements.forEach((el) => {\n el.classList.contains(`${CLASS_NAME}ssr`) && el.parentNode\n ? el.parentNode.removeChild(el)\n : el.className.replace(classRegex, '');\n });\n }\n };\n}\n\n/**\n * Provider to remove SSR styles on the browser\n */\nexport const BROWSER_PROVIDER = {\n provide: <InjectionToken<(() => void)[]>>APP_BOOTSTRAP_LISTENER,\n useFactory: removeStyles,\n deps: [DOCUMENT, PLATFORM_ID],\n multi: true,\n};\n\nexport const CLASS_NAME = 'flex-layout-';\n","import { NgModule } from '@angular/core';\n\nimport { BROWSER_PROVIDER } from './browser-provider';\n\n/**\n * *****************************************************************\n * Define module for common Angular Layout utilities\n * *****************************************************************\n */\n@NgModule({\n providers: [BROWSER_PROVIDER],\n})\nexport class CoreModule {}\n","import { Injectable } from '@angular/core';\n\n/**\n * Utility to emulate a CSS stylesheet\n *\n * This utility class stores all of the styles for a given HTML element\n * as a readonly `stylesheet` map.\n */\n@Injectable({ providedIn: 'root' })\nexport class StylesheetMap {\n readonly stylesheet = new Map<HTMLElement, Map<string, string | number>>();\n\n /**\n * Add an individual style to an HTML element\n */\n addStyleToElement(\n element: HTMLElement,\n style: string,\n value: string | number,\n ) {\n const stylesheet = this.stylesheet.get(element);\n if (stylesheet) {\n stylesheet.set(style, value);\n } else {\n this.stylesheet.set(element, new Map([[style, value]]));\n }\n }\n\n /**\n * Clear the virtual stylesheet\n */\n clearStyles() {\n this.stylesheet.clear();\n }\n\n /**\n * Retrieve a given style for an HTML element\n */\n getStyleForElement(el: HTMLElement, styleName: string): string {\n const styles = this.stylesheet.get(el);\n let value = '';\n if (styles) {\n const style = styles.get(styleName);\n if (typeof style === 'number' || typeof style === 'string') {\n value = style + '';\n }\n }\n return value;\n }\n}\n","import { InjectionToken } from '@angular/core';\nimport { BreakPoint } from '../breakpoints/break-point';\n\nexport const BREAKPOINT = new InjectionToken<BreakPoint | BreakPoint[] | null>(\n 'Flex Layout token, collect all breakpoints into one provider',\n {\n providedIn: 'root',\n factory: () => null,\n },\n);\n","import { InjectionToken } from '@angular/core';\nimport { Multiplier } from '../multiply/multiplier';\n\n/** a set of configuration options for FlexLayoutModule */\nexport interface LayoutConfigOptions {\n addFlexToParent?: boolean;\n addOrientationBps?: boolean;\n disableDefaultBps?: boolean;\n disableVendorPrefixes?: boolean;\n serverLoaded?: boolean;\n useColumnBasisZero?: boolean;\n printWithBreakpoints?: string[];\n mediaTriggerAutoRestore?: boolean;\n ssrObserveBreakpoints?: string[];\n multiplier?: Multiplier;\n defaultUnit?: string;\n detectLayoutDisplay?: boolean;\n}\n\nexport const DEFAULT_CONFIG: Required<LayoutConfigOptions> = {\n addFlexToParent: true,\n addOrientationBps: false,\n disableDefaultBps: false,\n disableVendorPrefixes: false,\n serverLoaded: false,\n useColumnBasisZero: true,\n printWithBreakpoints: [],\n mediaTriggerAutoRestore: true,\n ssrObserveBreakpoints: [],\n // This is disabled by default because otherwise the multiplier would\n // run for all users, regardless of whether they're using this feature.\n // Instead, we disable it by default, which requires this ugly cast.\n multiplier: undefined as unknown as Multiplier,\n defaultUnit: 'px',\n detectLayoutDisplay: false,\n};\n\nexport const LAYOUT_CONFIG = new InjectionToken<LayoutConfigOptions>(\n 'Flex Layout token, config options for the library',\n {\n providedIn: 'root',\n factory: () => DEFAULT_CONFIG,\n },\n);\n","import { InjectionToken } from '@angular/core';\n\n/**\n * Token that is provided to tell whether the FlexLayoutServerModule\n * has been included in the bundle\n *\n * NOTE: This can be manually provided to disable styles when using SSR\n */\nexport const SERVER_TOKEN = new InjectionToken<boolean>(\n 'FlexLayoutServerLoaded',\n {\n providedIn: 'root',\n factory: () => false,\n },\n);\n","interface WithPriority {\n priority?: number;\n}\n\n/** HOF to sort the breakpoints by descending priority */\nexport function sortDescendingPriority<T extends WithPriority>(\n a: T | null,\n b: T | null,\n): number {\n const priorityA = a ? a.priority || 0 : 0;\n const priorityB = b ? b.priority || 0 : 0;\n return priorityB - priorityA;\n}\n\n/** HOF to sort the breakpoints by ascending priority */\nexport function sortAscendingPriority<T extends WithPriority>(\n a: T,\n b: T,\n): number {\n const pA = a.priority || 0;\n const pB = b.priority || 0;\n return pA - pB;\n}\n","import { extendObject } from '@ngbracket/ngx-layout/_private-utils';\nimport { BreakPoint } from './break-point';\n\nconst ALIAS_DELIMITERS = /(\\.|-|_)/g;\nfunction firstUpperCase(part: string) {\n let first = part.length > 0 ? part.charAt(0) : '';\n let remainder = part.length > 1 ? part.slice(1) : '';\n return first.toUpperCase() + remainder;\n}\n\n/**\n * Converts snake-case to SnakeCase.\n * @param name Text to UpperCamelCase\n */\nfunction camelCase(name: string): string {\n return name\n .replace(ALIAS_DELIMITERS, '|')\n .split('|')\n .map(firstUpperCase)\n .join('');\n}\n\n/**\n * For each breakpoint, ensure that a Suffix is defined;\n * fallback to UpperCamelCase the unique Alias value\n */\nexport function validateSuffixes(list: BreakPoint[]): BreakPoint[] {\n list.forEach((bp: BreakPoint) => {\n if (!bp.suffix) {\n bp.suffix = camelCase(bp.alias); // create Suffix value based on alias\n bp.overlapping = !!bp.overlapping; // ensure default value\n }\n });\n return list;\n}\n\n/**\n * Merge a custom breakpoint list with the default list based on unique alias values\n * - Items are added if the alias is not in the default list\n * - Items are merged with the custom override if the alias exists in the default list\n */\nexport function mergeByAlias(\n defaults: BreakPoint[],\n custom: BreakPoint[] = [],\n): BreakPoint[] {\n const dict: { [key: string]: BreakPoint } = {};\n defaults.forEach((bp) => {\n dict[bp.alias] = bp;\n });\n // Merge custom breakpoints\n custom.forEach((bp: BreakPoint) => {\n if (dict[bp.alias]) {\n extendObject(dict[bp.alias], bp);\n } else {\n dict[bp.alias] = bp;\n }\n });\n\n return validateSuffixes(Object.keys(dict).map((k) => dict[k]));\n}\n","import { BreakPoint } from '../break-point';\n\n/**\n * NOTE: Smaller ranges have HIGHER priority since the match is more specific\n */\nexport const DEFAULT_BREAKPOINTS: BreakPoint[] = [\n {\n alias: 'xs',\n mediaQuery: 'screen and (min-width: 0px) and (max-width: 599.98px)',\n priority: 1000,\n },\n {\n alias: 'sm',\n mediaQuery: 'screen and (min-width: 600px) and (max-width: 959.98px)',\n priority: 900,\n },\n {\n alias: 'md',\n mediaQuery: 'screen and (min-width: 960px) and (max-width: 1279.98px)',\n priority: 800,\n },\n {\n alias: 'lg',\n mediaQuery: 'screen and (min-width: 1280px) and (max-width: 1919.98px)',\n priority: 700,\n },\n {\n alias: 'xl',\n mediaQuery: 'screen and (min-width: 1920px) and (max-width: 4999.98px)',\n priority: 600,\n },\n {\n alias: 'lt-sm',\n overlapping: true,\n mediaQuery: 'screen and (max-width: 599.98px)',\n priority: 950,\n },\n {\n alias: 'lt-md',\n overlapping: true,\n mediaQuery: 'screen and (max-width: 959.98px)',\n priority: 850,\n },\n {\n alias: 'lt-lg',\n overlapping: true,\n mediaQuery: 'screen and (max-width: 1279.98px)',\n priority: 750,\n },\n {\n alias: 'lt-xl',\n overlapping: true,\n priority: 650,\n mediaQuery: 'screen and (max-width: 1919.98px)',\n },\n {\n alias: 'gt-xs',\n overlapping: true,\n mediaQuery: 'screen and (min-width: 600px)',\n priority: -950,\n },\n {\n alias: 'gt-sm',\n overlapping: true,\n mediaQuery: 'screen and (min-width: 960px)',\n priority: -850,\n },\n {\n alias: 'gt-md',\n overlapping: true,\n mediaQuery: 'screen and (min-width: 1280px)',\n priority: -750,\n },\n {\n alias: 'gt-lg',\n overlapping: true,\n mediaQuery: 'screen and (min-width: 1920px)',\n priority: -650,\n },\n];\n","import { BreakPoint } from '../break-point';\n\n/* tslint:disable */\nconst HANDSET_PORTRAIT = '(orientation: portrait) and (max-width: 599.98px)';\nconst HANDSET_LANDSCAPE = '(orientation: landscape) and (max-width: 959.98px)';\n\nconst TABLET_PORTRAIT =\n '(orientation: portrait) and (min-width: 600px) and (max-width: 839.98px)';\nconst TABLET_LANDSCAPE =\n '(orientation: landscape) and (min-width: 960px) and (max-width: 1279.98px)';\n\nconst WEB_PORTRAIT = '(orientation: portrait) and (min-width: 840px)';\nconst WEB_LANDSCAPE = '(orientation: landscape) and (min-width: 1280px)';\n\nexport const ScreenTypes = {\n HANDSET: `${HANDSET_PORTRAIT}, ${HANDSET_LANDSCAPE}`,\n TABLET: `${TABLET_PORTRAIT} , ${TABLET_LANDSCAPE}`,\n WEB: `${WEB_PORTRAIT}, ${WEB_LANDSCAPE} `,\n\n HANDSET_PORTRAIT: `${HANDSET_PORTRAIT}`,\n TABLET_PORTRAIT: `${TABLET_PORTRAIT} `,\n WEB_PORTRAIT: `${WEB_PORTRAIT}`,\n\n HANDSET_LANDSCAPE: `${HANDSET_LANDSCAPE}`,\n TABLET_LANDSCAPE: `${TABLET_LANDSCAPE}`,\n WEB_LANDSCAPE: `${WEB_LANDSCAPE}`,\n};\n\n/**\n * Extended Breakpoints for handset/tablets with landscape or portrait orientations\n */\nexport const ORIENTATION_BREAKPOINTS: BreakPoint[] = [\n { alias: 'handset', priority: 2000, mediaQuery: ScreenTypes.HANDSET },\n {\n alias: 'handset.landscape',\n priority: 2000,\n mediaQuery: ScreenTypes.HANDSET_LANDSCAPE,\n },\n {\n alias: 'handset.portrait',\n priority: 2000,\n mediaQuery: ScreenTypes.HANDSET_PORTRAIT,\n },\n\n { alias: 'tablet', priority: 2100, mediaQuery: ScreenTypes.TABLET },\n {\n alias: 'tablet.landscape',\n priority: 2100,\n mediaQuery: ScreenTypes.TABLET_LANDSCAPE,\n },\n {\n alias: 'tablet.portrait',\n priority: 2100,\n mediaQuery: ScreenTypes.TABLET_PORTRAIT,\n },\n\n {\n alias: 'web',\n priority: 2200,\n mediaQuery: ScreenTypes.WEB,\n overlapping: true,\n },\n {\n alias: 'web.landscape',\n priority: 2200,\n mediaQuery: ScreenTypes.WEB_LANDSCAPE,\n overlapping: true,\n },\n {\n alias: 'web.portrait',\n priority: 2200,\n mediaQuery: ScreenTypes.WEB_PORTRAIT,\n overlapping: true,\n },\n];\n","import { inject, InjectionToken } from '@angular/core';\nimport { mergeByAlias } from '../breakpoints/breakpoint-tools';\nimport { DEFAULT_BREAKPOINTS } from '../breakpoints/data/break-points';\nimport { ORIENTATION_BREAKPOINTS } from '../breakpoints/data/orientation-break-points';\nimport { BREAKPOINT } from '../tokens/breakpoint-token';\nimport { LAYOUT_CONFIG } from '../tokens/library-config';\nimport { BreakPoint } from './break-point';\n\n/**\n * Injection token unique to the flex-layout library.\n * Use this token when build a custom provider (see below).\n */\nexport const BREAKPOINTS = new InjectionToken<BreakPoint[]>(\n 'Token (@ngbracket/ngx-layout) Breakpoints',\n {\n providedIn: 'root',\n factory: () => {\n const breakpoints: any = inject(BREAKPOINT);\n const layoutConfig = inject(LAYOUT_CONFIG);\n const bpFlattenArray: BreakPoint[] = [].concat.apply(\n [],\n (breakpoints || []).map((v: BreakPoint | BreakPoint[]) =>\n Array.isArray(v) ? v : [v],\n ),\n );\n const builtIns = (\n layoutConfig.disableDefaultBps ? [] : DEFAULT_BREAKPOINTS\n ).concat(layoutConfig.addOrientationBps ? ORIENTATION_BREAKPOINTS : []);\n\n return mergeByAlias(builtIns, bpFlattenArray);\n },\n },\n);\n","import { Inject, Injectable } from '@angular/core';\n\nimport { sortAscendingPriority } from '../utils/sort';\nimport { BreakPoint } from './break-point';\nimport { BREAKPOINTS } from './break-points-token';\n\nexport type OptionalBreakPoint = BreakPoint | null;\n\n/**\n * Registry of 1..n MediaQuery breakpoint ranges\n * This is published as a provider and may be overridden from custom, application-specific ranges\n *\n */\n@Injectable({ providedIn: 'root' })\nexport class BreakPointRegistry {\n readonly items: BreakPoint[];\n\n constructor(@Inject(BREAKPOINTS) list: BreakPoint[]) {\n this.items = [...list].sort(sortAscendingPriority);\n }\n\n /**\n * Search breakpoints by alias (e.g. gt-xs)\n */\n findByAlias(alias: string): OptionalBreakPoint {\n return !alias\n ? null\n : this.findWithPredicate(alias, (bp) => bp.alias === alias);\n }\n\n findByQuery(query: string): OptionalBreakPoint {\n return this.findWithPredicate(query, (bp) => bp.mediaQuery === query);\n }\n\n /**\n * Get all the breakpoints whose ranges could overlapping `normal` ranges;\n * e.g. gt-sm overlaps md, lg, and xl\n */\n get overlappings(): BreakPoint[] {\n return this.items.filter((it) => it.overlapping);\n }\n\n /**\n * Get list of all registered (non-empty) breakpoint aliases\n */\n get aliases(): string[] {\n return this.items.map((it) => it.alias);\n }\n\n /**\n * Aliases are mapped to properties using suffixes\n * e.g. 'gt-sm' for property 'layout' uses suffix 'GtSm'\n * for property layoutGtSM.\n */\n get suffixes(): string[] {\n return this.items.map((it) => it?.suffix ?? '');\n }\n\n /**\n * Memoized lookup using custom predicate function\n */\n private findWithPredicate(\n key: string,\n searchFn: (bp: BreakPoint) => boolean,\n ): OptionalBreakPoint {\n let response = this.findByMap.get(key);\n if (!response) {\n response = this.items.find(searchFn) ?? null;\n this.findByMap.set(key, response);\n }\n return response ?? null;\n }\n\n /**\n * Memoized BreakPoint Lookups\n */\n private readonly findByMap = new Map<String, OptionalBreakPoint>();\n}\n","/* eslint-disable @typescript-eslint/no-empty-function */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n CSP_NONCE,\n DOCUMENT,\n Inject,\n Injectable,\n NgZone,\n OnDestroy,\n Optional,\n PLATFORM_ID,\n} from '@angular/core';\nimport { BehaviorSubject, merge, Observable, Observer } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\nimport { MediaChange } from '../media-change';\n\n/**\n * MediaMonitor configures listeners to mediaQuery changes and publishes an Observable facade to\n * convert mediaQuery change callbacks to subscriber notifications. These notifications will be\n * performed within the ng Zone to trigger change detections and component updates.\n *\n * NOTE: both mediaQuery activations and de-activations are announced in notifications\n */\n@Injectable({ providedIn: 'root' })\nexport class MatchMedia implements OnDestroy {\n /** Initialize source with 'all' so all non-responsive APIs trigger style updates */\n readonly source = new BehaviorSubject<MediaChange>(new MediaChange(true));\n registry = new Map<string, MediaQueryList>();\n private readonly pendingRemoveListenerFns: Array<() => void> = [];\n\n constructor(\n protected _zone: NgZone,\n @Inject(PLATFORM_ID) protected _platformId: object,\n @Inject(DOCUMENT) protected _document: any,\n @Optional() @Inject(CSP_NONCE) protected _nonce?: string | null,\n ) {}\n\n /**\n * Publish list of all current activations\n */\n get activations(): string[] {\n const results: string[] = [];\n this.registry.forEach((mql: MediaQueryList, key: string) => {\n if (mql.matches) {\n results.push(key);\n }\n });\n return results;\n }\n\n /**\n * For the specified mediaQuery?\n */\n isActive(mediaQuery: string): boolean {\n const mql = this.registry.get(mediaQuery);\n return (\n mql?.matches ?? this.registerQuery(mediaQuery).some((m) => m.matches)\n );\n }\n\n /**\n * External observers can watch for all (or a specific) mql changes.\n *\n * If a mediaQuery is not specified, then ALL mediaQuery activations will\n * be announced.\n */\n observe(): Observable<MediaChange>;\n observe(mediaQueries: string[]): Observable<MediaChange>;\n observe(\n mediaQueries: string[],\n filterOthers: boolean,\n ): Observable<MediaChange>;\n\n /**\n * External observers can watch for all (or a specific) mql changes.\n * Typically used by the MediaQueryAdaptor; optionally available to components\n * who wish to use the MediaMonitor as mediaMonitor$ observable service.\n *\n * Use deferred registration process to register breakpoints only on subscription\n * This logic also enforces logic to register all mediaQueries BEFORE notify\n * subscribers of notifications.\n */\n observe(mqList?: string[], filterOthers = false): Observable<MediaChange> {\n if (mqList && mqList.length) {\n const matchMedia$: Observable<MediaChange> = this._observable$.pipe(\n filter((change: MediaChange) =>\n !filterOthers ? true : mqList.indexOf(change.mediaQuery) > -1,\n ),\n );\n const registration$: Observable<MediaChange> = new Observable(\n (observer: Observer<MediaChange>) => {\n // tslint:disable-line:max-line-length\n const matches: Array<MediaChange> = this.registerQuery(mqList);\n if (matches.length) {\n const lastChange = matches.pop()!;\n matches.forEach((e: MediaChange) => {\n observer.next(e);\n });\n this.source.next(lastChange); // last match is cached\n }\n observer.complete();\n },\n );\n return merge(registration$, matchMedia$);\n }\n\n return this._observable$;\n }\n\n /**\n * Based on the BreakPointRegistry provider, register internal listeners for each unique\n * mediaQuery. Each listener emits specific MediaChange data to observers\n */\n registerQuery(mediaQuery: string | string[]) {\n const list = Array.isArray(mediaQuery) ? mediaQuery : [mediaQuery];\n const matches: MediaChange[] = [];\n\n buildQueryCss(list, this._document, this._nonce);\n\n list.forEach((query: string) => {\n const onMQLEvent = (e: MediaQueryListEvent) => {\n this._zone.run(() =>\n this.source.next(new MediaChange(e.matches, query)),\n );\n };\n\n let mql = this.registry.get(query);\n if (!mql) {\n mql = this.buildMQL(query);\n mql.addListener(onMQLEvent);\n this.pendingRemoveListenerFns.push(() =>\n mql!.removeListener(onMQLEvent),\n );\n this.registry.set(query, mql);\n }\n\n if (mql.matches) {\n matches.push(new MediaChange(true, query));\n }\n });\n\n return matches;\n }\n\n ngOnDestroy(): void {\n let fn;\n while ((fn = this.pendingRemoveListenerFns.pop())) {\n fn();\n }\n }\n\n /**\n * Call window.matchMedia() to build a MediaQueryList; which\n * supports 0..n listeners for activation/deactivation\n */\n protected buildMQL(query: string): MediaQueryList {\n return constructMql(query, isPlatformBrowser(this._platformId));\n }\n\n protected _observable$ = this.source.asObservable();\n}\n\n/**\n * Private global registry for all dynamically-created, injected style tags\n * @see prepare(query)\n */\nconst ALL_STYLES: { [key: string]: any } = {};\n\n/**\n * For Webkit engines that only trigger the MediaQueryList Listener\n * when there is at least one CSS selector for the respective media query.\n *\n * @param mediaQueries\n * @param _document\n */\nfunction buildQueryCss(\n mediaQueries: string[],\n _document: Document,\n _nonce?: string | null,\n) {\n const list = mediaQueries.filter((it) => !ALL_STYLES[it]);\n if (list.length > 0) {\n const query = list.join(', ');\n\n try {\n const styleEl = _document.createElement('style');\n\n styleEl.setAttribute('type', 'text/css');\n if (_nonce) {\n styleEl.setAttribute('nonce', _nonce);\n }\n if (!(styleEl as any).styleSheet) {\n const cssText = `\n/*\n @ngbracket/ngx-layout - workaround for possible browser quirk with mediaQuery listeners\n see http://bit.ly/2sd4HMP\n*/\n@media ${query} {.fx-query-test{ }}\n`;\n styleEl.appendChild(_document.createTextNode(cssText));\n }\n\n _document.head!.appendChild(styleEl);\n\n // Store in private global registry\n list.forEach((mq) => (ALL_STYLES[mq] = styleEl));\n } catch (e) {\n console.error(e);\n }\n }\n}\n\nfunction buildMockMql(query: string) {\n const et: any = new EventTarget();\n et.matches = query === 'all' || query === '';\n et.media = query;\n et.addListener = () => {};\n et.removeListener = () => {};\n et.addEventListener = () => {};\n et.dispatchEvent = () => false;\n et.onchange = null;\n\n return et as MediaQueryList;\n}\n\nfunction constructMql(query: string, isBrowser: boolean): MediaQueryList {\n const canListen =\n isBrowser && !!(<Window>window).matchMedia('all').addListener;\n\n return canListen ? (<Window>window).matchMedia(query) : buildMockMql(query);\n}\n","import { Inject, Injectable, OnDestroy, DOCUMENT } from '@angular/core';\n\n\nimport { mergeAlias } from '../add-alias';\nimport { BreakPoint } from '../breakpoints/break-point';\nimport {\n BreakPointRegistry,\n OptionalBreakPoint,\n} from '../breakpoints/break-point-registry';\nimport { MediaChange } from '../media-change';\nimport { LAYOUT_CONFIG, LayoutConfigOptions } from '../tokens/library-config';\nimport { sortDescendingPriority } from '../utils/sort';\n\n/**\n * Interface to apply PrintHook to call anonymous `target.updateStyles()`\n */\nexport interface HookTarget {\n activatedBreakpoints: BreakPoint[];\n updateStyles(): void;\n}\n\nconst PRINT = 'print';\nexport const BREAKPOINT_PRINT = {\n alias: PRINT,\n mediaQuery: PRINT,\n priority: 1000,\n};\n\n/**\n * PrintHook - Use to intercept print MediaQuery activations and force\n * layouts to render with the specified print alias/breakpoint\n *\n * Used in MediaMarshaller and MediaObserver\n */\n@Injectable({ providedIn: 'root' })\nexport class PrintHook implements OnDestroy {\n constructor(\n protected breakpoints: BreakPointRegistry,\n @Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions,\n @Inject(DOCUMENT) protected _document: any,\n ) {}\n\n /** Add 'print' mediaQuery: to listen for matchMedia activations */\n withPrintQuery(queries: string[]): string[] {\n return [...queries, PRINT];\n }\n\n /** Is the MediaChange event for any 'print' @media */\n isPrintEvent(e: MediaChange): boolean {\n return e.mediaQuery.startsWith(PRINT);\n }\n\n /** What is the desired mqAlias to use while printing? */\n get printAlias(): string[] {\n return [...(this.layoutConfig.printWithBreakpoints ?? [])];\n }\n\n /** Lookup breakpoints associated with print aliases. */\n get printBreakPoints(): BreakPoint[] {\n return this.printAlias\n .map((alias) => this.breakpoints.findByAlias(alias))\n .filter((bp) => bp !== null) as BreakPoint[];\n }\n\n /** Lookup breakpoint associated with mediaQuery */\n getEventBreakpoints({ mediaQuery }: MediaChange): BreakPoint[] {\n const bp = this.breakpoints.findByQuery(mediaQuery);\n const list = bp ? [...this.printBreakPoints, bp] : this.printBreakPoints;\n\n return list.sort(sortDescendingPriority);\n }\n\n /** Update event with printAlias mediaQuery information */\n updateEvent(event: MediaChange): MediaChange {\n let bp: OptionalBreakPoint = this.breakpoints.findByQuery(event.mediaQuery);\n\n if (this.isPrintEvent(event)) {\n // Reset from 'print' to first (highest priority) print breakpoint\n bp = this.getEventBreakpoints(event)[0];\n event.mediaQuery = bp?.mediaQuery ?? '';\n }\n\n return mergeAlias(event, bp);\n }\n\n // registeredBeforeAfterPrintHooks tracks if we registered the `beforeprint`\n // and `afterprint` event listeners.\n private registeredBeforeAfterPrintHooks = false;\n\n // isPrintingBeforeAfterEvent is used to track if we are printing from within\n // a `beforeprint` event handler. This prevents the typical `stopPrinting`\n // form `interceptEvents` so that printing is not stopped while the dialog\n // is still open. This is an extension of the `isPrinting` property on\n // browsers which support `beforeprint` and `afterprint` events.\n private isPrintingBeforeAfterEvent = false;\n\n private beforePrintEventListeners: Function[] = [];\n private afterPrintEventListeners: Function[] = [];\n\n private formerActivations: Array<BreakPoint> | null = null;\n\n // registerBeforeAfterPrintHooks registers a `beforeprint` event hook so we can\n // trigger print styles synchronously and apply proper layout styles.\n // It is a noop if the hooks have already been registered or if the document's\n // `defaultView` is not available.\n registerBeforeAfterPrintHooks(target: HookTarget) {\n // `defaultView` may be null when rendering on the server or in other contexts.\n if (!this._document.defaultView || this.registeredBeforeAfterPrintHooks) {\n return;\n }\n\n this.registeredBeforeAfterPrintHooks = true;\n\n const beforePrintListener = () => {\n // If we aren't already printing, start printing and update the styles as\n // if there was a regular print `MediaChange`(from matchMedia).\n if (!this.isPrinting) {\n this.isPrintingBeforeAfterEvent = true;\n this.startPrinting(\n target,\n this.getEventBreakpoints(new MediaChange(true, PRINT)),\n );\n target.updateStyles();\n }\n };\n\n const afterPrintListener = () => {\n // If we aren't already printing, start printing and update the styles as\n // if there was a regular print `MediaChange`(from matchMedia).\n this.isPrintingBeforeAfterEvent = false;\n if (this.isPrinting) {\n this.stopPrinting(target);\n target.updateStyles();\n }\n };\n\n // Could we have teardown logic to remove if there are no print listeners being used?\n this._document.defaultView.addEventListener(\n 'beforeprint',\n beforePrintListener,\n );\n this._document.defaultView.addEventListener(\n 'afterprint',\n afterPrintListener,\n );\n\n this.beforePrintEventListeners.push(beforePrintListener);\n this.afterPrintEventListeners.push(afterPrintListener);\n }\n\n /**\n * Prepare RxJS tap operator with partial application\n * @return pipeable tap predicate\n */\n interceptEvents(target: HookTarget) {\n return (event: MediaChange) => {\n if (this.isPrintEvent(event)) {\n if (event.matches && !this.isPrinting) {\n this.startPrinting(target, this.getEventBreakpoints(event));\n target.updateStyles();\n } else if (\n !event.matches &&\n this.isPrinting &&\n !this.isPrintingBeforeAfterEvent\n ) {\n this.stopPrinting(target);\n target.updateStyles();\n }\n\n return;\n }\n\n this.collectActivations(target, event);\n };\n }\n\n /** Stop mediaChange event propagation in event streams */\n blockPropagation() {\n return (event: MediaChange): boolean => {\n return !(this.isPrinting || this.isPrintEvent(event));\n };\n }\n\n /**\n * Save current activateBreakpoints (for later restore)\n * and substitute only the printAlias breakpoint\n */\n protected startPrinting(target: HookTarget, bpList: OptionalBreakPoint[]) {\n this.isPrinting = true;\n this.formerActivations = target.activatedBreakpoints;\n target.activatedBreakpoints = this.queue.addPrintBreakpoints(bpList);\n }\n\n /** For any print de-activations, reset the entire print queue */\n protected stopPrinting(target: HookTarget) {\n target.activatedBreakpoints = this.deactivations;\n this.deactivations = [];\n this.formerActivations = null;\n this.queue.clear();\n this.isPrinting = false;\n }\n\n /**\n * To restore pre-Print Activations, we must capture the proper\n * list of breakpoint activations BEFORE print starts. OnBeforePrint()\n * is supported; so 'print' mediaQuery activations are used as a fallback\n * in browsers without `beforeprint` support.\n *\n * > But activated breakpoints are deactivated BEFORE 'print' activation.\n *\n * Let's capture all de-activations using the following logic:\n *\n * When not printing:\n * - clear cache when activating non-print breakpoint\n * - update cache (and sort) when deactivating\n *\n * When printing:\n * - sort and save when starting print\n * - restore as activatedTargets and clear when stop printing\n */\n collectActivations(target: HookTarget, event: MediaChange) {\n if (!this.isPrinting || this.isPrintingBeforeAfterEvent) {\n if (!this.isPrintingBeforeAfterEvent) {\n // Only clear deactivations if we aren't printing from a `beforeprint` event.\n // Otherwise, this will clear before `stopPrinting()` is called to restore\n // the pre-Print Activations.\n this.deactivations = [];\n\n return;\n }\n\n if (!event.matches) {\n const bp = this.breakpoints.findByQuery(event.mediaQuery);\n // Deactivating a breakpoint\n if (bp) {\n const hasFormerBp =\n this.formerActivations && this.formerActivations.includes(bp);\n const wasActivated =\n !this.formerActivations && target.activatedBreakpoints.includes(bp);\n const shouldDeactivate = hasFormerBp || wasActivated;\n if (shouldDeactivate) {\n this.deactivations.push(bp);\n this.deactivations.sort(sortDescendingPriority);\n }\n }\n }\n }\n }\n\n /** Teardown logic for the service. */\n ngOnDestroy() {\n if (this._document.defaultView) {\n this.beforePrintEventListeners.forEach((l) =>\n this._document.defaultView.removeEventListener('beforeprint', l),\n );\n this.afterPrintEventListeners.forEach((l) =>\n this._document.defaultView.removeEventListener('afterprint', l),\n );\n }\n }\n\n // Is this service currently in print mode\n private isPrinting = false;\n private queue = new PrintQueue();\n private deactivations: BreakPoint[] = [];\n}\n\n// ************************************************************************\n// Internal Utility class 'PrintQueue'\n// ************************************************************************\n\n/**\n * Utility class to manage print breakpoints + activatedBreakpoints\n * with correct sorting WHILE printing\n */\nclass PrintQueue {\n /** Sorted queue with prioritized print breakpoints */\n printBreakpoints: BreakPoint[] = [];\n\n addPrintBreakpoints(bpList: OptionalBreakPoint[]): BreakPoint[] {\n bpList.push(BREAKPOINT_PRINT);\n bpList.sort(sortDescendingPriority);\n bpList.forEach((bp) => this.addBreakpoint(bp));\n\n return this.printBreakpoints;\n }\n\n /** Add Print breakpoint to queue */\n addBreakpoint(bp: OptionalBreakPoint) {\n if (!!bp) {\n const bpInList = this.printBreakpoints.find(\n (it) => it.mediaQuery === bp.mediaQuery,\n );\n\n if (bpInList === undefined) {\n // If this is a `printAlias` breakpoint, then append. If a true 'print' breakpoint,\n // register as highest priority in the queue\n this.printBreakpoints = isPrintBreakPoint(bp)\n ? [bp, ...this.printBreakpoints]\n : [...this.printBreakpoints, bp];\n }\n }\n }\n\n /** Restore original activated breakpoints and clear internal caches */\n clear() {\n this.printBreakpoints = [];\n }\n}\n\n// ************************************************************************\n// Internal Utility methods\n// ************************************************************************\n\n/** Only support intercept queueing if the Breakpoint is a print @media query */\nfunction isPrintBreakPoint(bp: OptionalBreakPoint): boolean {\n return bp?.mediaQuery.startsWith(PRINT) ?? false;\n}\n","import { Injectable } from '@angular/core';\n\nimport { merge, Observable, Subject, Subscription } from 'rxjs';\nimport { filter, tap } from 'rxjs/operators';\n\nimport { BreakPoint } from '../breakpoints/break-point';\nimport { BreakPointRegistry } from '../breakpoints/break-point-registry';\nimport { MatchMedia } from '../match-media/match-media';\nimport { MediaChange } from '../media-change';\nimport { sortDescendingPriority } from '../utils/sort';\n\nimport { mergeAlias } from '../add-alias';\nimport { PrintHook } from './print-hook';\n\ntype ClearCallback = () => void;\ntype UpdateCallback = (val: any) => void;\ntype Builder = UpdateCallback | ClearCallback;\n\ntype ValueMap = Map<string, string>;\ntype BreakpointMap = Map<string, ValueMap>;\ntype ElementMap = Map<HTMLElement, BreakpointMap>;\ntype ElementKeyMap = WeakMap<HTMLElement, Set<string>>;\ntype SubscriptionMap = Map<string, Subscription>;\ntype WatcherMap = WeakMap<HTMLElement, SubscriptionMap>;\ntype BuilderMap = WeakMap<HTMLElement, Map<string, Builder>>;\n\nexport interface ElementMatcher {\n element: HTMLElement;\n key: string;\n value: any;\n}\n\n/**\n * MediaMarshaller - register responsive values from directives and\n * trigger them based on media query events\n */\n@Injectable({ providedIn: 'root' })\nexport class MediaMarshaller {\n private _useFallbacks = true;\n private _activatedBreakpoints: BreakPoint[] = [];\n private elementMap: ElementMap = new Map();\n private elementKeyMap: ElementKeyMap = new WeakMap();\n private watcherMap: WatcherMap = new WeakMap(); // special triggers to update elements\n private updateMap: BuilderMap = new WeakMap(); // callback functions to update styles\n private clearMap: BuilderMap = new WeakMap(); // callback functions to clear styles\n\n private subject: Subject<ElementMatcher> = new Subject();\n\n get activatedAlias(): string {\n return this.activatedBreakpoints[0]?.alias ?? '';\n }\n\n set activatedBreakpoints(bps: BreakPoint[]) {\n this._activatedBreakpoints = [...bps];\n }\n\n get activatedBreakpoints(): BreakPoint[] {\n return [...this._activatedBreakpoints];\n }\n\n set useFallbacks(value: boolean) {\n this._useFallbacks = value;\n }\n\n constructor(\n protected matchMedia: MatchMedia,\n protected breakpoints: BreakPointRegistry,\n protected hook: PrintHook,\n ) {\n this.observeActivations();\n }\n\n /**\n * Update styles on breakpoint activates or deactivates\n * @param mc\n */\n onMediaChange(mc: MediaChange) {\n const bp: BreakPoint | null = this.findByQuery(mc.mediaQuery);\n\n if (bp) {\n mc = mergeAlias(mc, bp);\n\n const bpIndex = this.activatedBreakpoints.indexOf(bp);\n\n if (mc.matches && bpIndex === -1) {\n this._activatedBreakpoints.push(bp);\n this._activatedBreakpoints.sort(sortDescendingPriority);\n\n this.updateStyles();\n } else if (!mc.matches && bpIndex !== -1) {\n // Remove the breakpoint when it's deactivated\n this._activatedBreakpoints.splice(bpIndex, 1);\n this._activatedBreakpoints.sort(sortDescendingPriority);\n\n this.updateStyles();\n }\n }\n }\n\n /**\n * initialize the marshaller with necessary elements for delegation on an element\n * @param element\n * @param key\n * @param updateFn optional callback so that custom bp directives don't have to re-provide this\n * @param clearFn optional callback so that custom bp directives don't have to re-provide this\n * @param extraTriggers other triggers to force style updates (e.g. layout, directionality, etc)\n */\n init(\n element: HTMLElement,\n key: string,\n updateFn?: UpdateCallback,\n clearFn?: ClearCallback,\n extraTriggers: Observable<any>[] = [],\n ): void {\n initBuilderMap(this.updateMap, element, key, updateFn);\n initBuilderMap(this.clearMap, element, key, clearFn);\n\n this.buildElementKeyMap(element, key);\n this.watchExtraTriggers(element, key, extraTriggers);\n }\n\n /**\n * get the value for an element and key and optionally a given breakpoint\n * @param element\n * @param key\n * @param bp\n */\n getValue(element: HTMLElement, key: string, bp?: string): any {\n const bpMap = this.elementMap.get(element);\n if (bpMap) {\n const values =\n bp !== undefined ? bpMap.get(bp) : this.getActivatedValues(bpMap, key);\n if (values) {\n return values.get(key);\n }\n }\n return undefined;\n }\n\n /**\n * whether the element has values for a given key\n * @param element\n * @param key\n */\n hasValue(element: HTMLElement, key: string): boolean {\n const bpMap = this.elementMap.get(element);\n if (bpMap) {\n const values = this.getActivatedValues(bpMap, key);\n if (values) {\n return values.get(key) !== undefined || false;\n }\n }\n return false;\n }\n\n /**\n * Set the value for an input on a directive\n * @param element the element in question\n * @param key the type of the directive (e.g. flex, layout-gap, etc)\n * @param bp the breakpoint suffix (empty string = default)\n * @param val the value for the breakpoint\n */\n setValue(element: HTMLElement, key: string, val: any, bp: string): void {\n let bpMap: BreakpointMap | undefined = this.elementMap.get(element);\n if (!bpMap) {\n bpMap = new Map().set(bp, new Map().set(key, val));\n this.elementMap.set(element, bpMap);\n } else {\n const values = (bpMap.get(bp) ?? new Map()).set(key, val);\n bpMap.set(bp, values);\n this.elementMap.set(element, bpMap);\n }\n const value = this.getValue(element, key);\n if (value !== undefined) {\n this.updateElement(element, key, value);\n }\n }\n\n /** Track element value changes for a specific key */\n trackValue(element: HTMLElement, key: string): Observable<ElementMatcher> {\n return this.subject\n .asObservable()\n .pipe(filter((v) => v.element === element && v.key === key));\n }\n\n /** update all styles for all elements on the current breakpoint */\n updateStyles(): void {\n this.elementMap.forEach((bpMap, el) => {\n const keyMap = new Set(this.elementKeyMap.get(el)!);\n let valueMap = this.getActivatedValues(bpMap);\n\n if (valueMap) {\n valueMap.forEach((v, k) => {\n this.updateElement(el, k, v);\n keyMap.delete(k);\n });\n }\n\n keyMap.forEach((k) => {\n valueMap = this.getActivatedValues(bpMap, k);\n if (valueMap) {\n const value = valueMap.get(k);\n this.updateElement(el, k, value);\n } else {\n this.clearElement(el, k);\n }\n });\n });\n }\n\n /**\n * clear the styles for a given element\n * @param element\n * @param key\n */\n clearElement(element: HTMLElement, key: string): void {\n const builders = this.clearMap.get(element);\n\n if (builders) {\n const clearFn: ClearCallback = builders.get(key) as ClearCallback;\n if (!!clearFn) {\n clearFn();\n this.subject.next({ element, key, value: '' });\n }\n }\n }\n\n /**\n * update a given element with the activated values for a given key\n * @param element\n * @param key\n * @param value\n */\n updateElement(element: HTMLElement, key: string, value: any): void {\n const builders = this.updateMap.get(element);\n if (builders) {\n const updateFn: UpdateCallback = builders.get(key) as UpdateCallback;\n if (!!updateFn) {\n updateFn(value);\n this.subject.next({ element, key, value });\n }\n }\n }\n\n /**\n * release all references to a given element\n * @param element\n */\n releaseElement(element: HTMLElement): void {\n const watcherMap = this.watcherMap.get(element);\n if (watcherMap) {\n watcherMap.forEach((s) => s.unsubscribe());\n this.watcherMap.delete(element);\n }\n const elementMap = this.elementMap.get(element);\n if (elementMap) {\n elementMap.forEach((_, s) => elementMap.delete(s));\n this.elementMap.delete(element);\n }\n }\n\n /**\n * trigger an update for a given element and key (e.g. layout)\n * @param element\n * @param key\n */\n triggerUpdate(element: HTMLElement, key?: string): void {\n const bpMap = this.elementMap.get(element);\n if (bpMap) {\n const valueMap = this.getActivatedValues(bpMap, key);\n if (valueMap) {\n if (key) {\n this.updateElement(element, key, valueMap.get(key));\n } else {\n valueMap.forEach((v, k) => this.updateElement(element, k, v));\n }\n }\n }\n }\n\n /** Cross-reference for HTMLElement with directive key */\n private buildElementKeyMap(element: HTMLElement, key: string) {\n let keyMap = this.elementKeyMap.get(element);\n if (!keyMap) {\n keyMap = new Set();\n this.elementKeyMap.set(element, keyMap);\n }\n keyMap.add(key);\n }\n\n /**\n * Other triggers that should force style updates:\n * - directionality\n * - layout changes\n * - mutationobserver updates\n */\n private watchExtraTriggers(\n element: HTMLElement,\n key: string,\n triggers: Observable<any>[],\n ) {\n if (triggers && triggers.length) {\n let watchers = this.watcherMap.get(element);\n if (!watchers) {\n watchers = new Map();\n this.watcherMap.set(element, watchers);\n }\n const subscription = watchers.get(key);\n if (!subscription) {\n const newSubscription = merge(...triggers).subscribe(() => {\n const currentValue = this.getValue(element, key);\n this.updateElement(element, key, currentValue);\n });\n watchers.set(key, newSubscription);\n }\n }\n }\n\n /** Breakpoint locator by mediaQuery */\n private findByQuery(query: string) {\n return this.breakpoints.findByQuery(query);\n }\n\n /**\n * get the fallback breakpoint for a given element, starting with the current breakpoint\n * @param bpMap\n * @param key\n */\n private getActivatedValues(\n bpMap: BreakpointMap,\n key?: string,\n ): ValueMap | undefined {\n for (let i = 0; i < this.activatedBreakpoints.length; i++) {\n const activatedBp = this.activatedBreakpoints[i];\n const valueMap = bpMap.get(activatedBp.alias);\n\n if (valueMap) {\n if (\n key === undefined ||\n (valueMap.has(key) && valueMap.get(key) != null)\n ) {\n return valueMap;\n }\n }\n }\n\n // On the server, we explicitly have an \"all\" section filled in to begin with.\n // So we don't need to aggressively find a fallback if no explicit value exists.\n if (!this._useFallbacks) {\n return undefined;\n }\n\n const lastHope = bpMap.get('');\n return key === undefined || (lastHope && lastHope.has(key))\n ? lastHope\n : undefined;\n }\n\n /**\n * Watch for mediaQuery breakpoint activations\n */\n private observeActivations() {\n const queries = this.breakpoints.items.map((bp) => bp.mediaQuery);\n\n this.hook.registerBeforeAfterPrintHooks(this);\n this.matchMedia\n .observe(this.hook.withPrintQuery(queries))\n .pipe(\n tap(this.hook.interceptEvents(this)),\n filter(this.hook.blockPropagation()),\n )\n .subscribe(this.onMediaChange.bind(this));\n }\n}\n\nfunction initBuilderMap(\n map: BuilderMap,\n element: HTMLElement,\n key: string,\n input?: Builder,\n): void {\n if (input !== undefined) {\n const oldMap = map.get(element) ?? new Map();\n oldMap.set(key, input);\n map.set(element, oldMap);\n }\n}\n","import { StyleDefinition } from '../style-utils/style-utils';\n\n/** A class that encapsulates CSS style generation for common directives */\nexport abstract class StyleBuilder {\n /** Whether to cache the generated output styles */\n shouldCache = true;\n\n /** Build the styles given an input string and configuration object from a host */\n abstract buildStyles(input: string, parent?: object): StyleDefinition;\n\n /**\n * Run a side effect computation given the input string and the computed styles\n * from the build task and the host configuration object\n * NOTE: This should be a no-op unless an algorithm is provided in a subclass\n */\n sideEffect(_input: string, _styles: StyleDefinition, _parent?: object) {}\n}\n","import { isPlatformBrowser, isPlatformServer } from '@angular/common';\nimport { Inject, Injectable, PLATFORM_ID } from '@angular/core';\n\nimport { applyCssPrefixes } from '@ngbracket/ngx-layout/_private-utils';\nimport { StylesheetMap } from '../stylesheet-map/stylesheet-map';\nimport { LAYOUT_CONFIG, LayoutConfigOptions } from '../tokens/library-config';\nimport { SERVER_TOKEN } from '../tokens/server-token';\n\n@Injectable({ providedIn: 'root' })\nexport class StyleUtils {\n