layout.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import * as i0 from '@angular/core';
  2. import { NgModule, CSP_NONCE, Injectable, Optional, Inject } from '@angular/core';
  3. import { coerceArray } from '@angular/cdk/coercion';
  4. import { Subject, combineLatest, concat, Observable } from 'rxjs';
  5. import { take, skip, debounceTime, map, startWith, takeUntil } from 'rxjs/operators';
  6. import * as i1 from '@angular/cdk/platform';
  7. class LayoutModule {
  8. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: LayoutModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
  9. static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.0", ngImport: i0, type: LayoutModule }); }
  10. static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: LayoutModule }); }
  11. }
  12. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: LayoutModule, decorators: [{
  13. type: NgModule,
  14. args: [{}]
  15. }] });
  16. /** Global registry for all dynamically-created, injected media queries. */
  17. const mediaQueriesForWebkitCompatibility = new Set();
  18. /** Style tag that holds all of the dynamically-created media queries. */
  19. let mediaQueryStyleNode;
  20. /** A utility for calling matchMedia queries. */
  21. class MediaMatcher {
  22. constructor(_platform, _nonce) {
  23. this._platform = _platform;
  24. this._nonce = _nonce;
  25. this._matchMedia =
  26. this._platform.isBrowser && window.matchMedia
  27. ? // matchMedia is bound to the window scope intentionally as it is an illegal invocation to
  28. // call it from a different scope.
  29. window.matchMedia.bind(window)
  30. : noopMatchMedia;
  31. }
  32. /**
  33. * Evaluates the given media query and returns the native MediaQueryList from which results
  34. * can be retrieved.
  35. * Confirms the layout engine will trigger for the selector query provided and returns the
  36. * MediaQueryList for the query provided.
  37. */
  38. matchMedia(query) {
  39. if (this._platform.WEBKIT || this._platform.BLINK) {
  40. createEmptyStyleRule(query, this._nonce);
  41. }
  42. return this._matchMedia(query);
  43. }
  44. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MediaMatcher, deps: [{ token: i1.Platform }, { token: CSP_NONCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
  45. static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MediaMatcher, providedIn: 'root' }); }
  46. }
  47. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MediaMatcher, decorators: [{
  48. type: Injectable,
  49. args: [{ providedIn: 'root' }]
  50. }], ctorParameters: function () { return [{ type: i1.Platform }, { type: undefined, decorators: [{
  51. type: Optional
  52. }, {
  53. type: Inject,
  54. args: [CSP_NONCE]
  55. }] }]; } });
  56. /**
  57. * Creates an empty stylesheet that is used to work around browser inconsistencies related to
  58. * `matchMedia`. At the time of writing, it handles the following cases:
  59. * 1. On WebKit browsers, a media query has to have at least one rule in order for `matchMedia`
  60. * to fire. We work around it by declaring a dummy stylesheet with a `@media` declaration.
  61. * 2. In some cases Blink browsers will stop firing the `matchMedia` listener if none of the rules
  62. * inside the `@media` match existing elements on the page. We work around it by having one rule
  63. * targeting the `body`. See https://github.com/angular/components/issues/23546.
  64. */
  65. function createEmptyStyleRule(query, nonce) {
  66. if (mediaQueriesForWebkitCompatibility.has(query)) {
  67. return;
  68. }
  69. try {
  70. if (!mediaQueryStyleNode) {
  71. mediaQueryStyleNode = document.createElement('style');
  72. if (nonce) {
  73. mediaQueryStyleNode.nonce = nonce;
  74. }
  75. mediaQueryStyleNode.setAttribute('type', 'text/css');
  76. document.head.appendChild(mediaQueryStyleNode);
  77. }
  78. if (mediaQueryStyleNode.sheet) {
  79. mediaQueryStyleNode.sheet.insertRule(`@media ${query} {body{ }}`, 0);
  80. mediaQueriesForWebkitCompatibility.add(query);
  81. }
  82. }
  83. catch (e) {
  84. console.error(e);
  85. }
  86. }
  87. /** No-op matchMedia replacement for non-browser platforms. */
  88. function noopMatchMedia(query) {
  89. // Use `as any` here to avoid adding additional necessary properties for
  90. // the noop matcher.
  91. return {
  92. matches: query === 'all' || query === '',
  93. media: query,
  94. addListener: () => { },
  95. removeListener: () => { },
  96. };
  97. }
  98. /** Utility for checking the matching state of @media queries. */
  99. class BreakpointObserver {
  100. constructor(_mediaMatcher, _zone) {
  101. this._mediaMatcher = _mediaMatcher;
  102. this._zone = _zone;
  103. /** A map of all media queries currently being listened for. */
  104. this._queries = new Map();
  105. /** A subject for all other observables to takeUntil based on. */
  106. this._destroySubject = new Subject();
  107. }
  108. /** Completes the active subject, signalling to all other observables to complete. */
  109. ngOnDestroy() {
  110. this._destroySubject.next();
  111. this._destroySubject.complete();
  112. }
  113. /**
  114. * Whether one or more media queries match the current viewport size.
  115. * @param value One or more media queries to check.
  116. * @returns Whether any of the media queries match.
  117. */
  118. isMatched(value) {
  119. const queries = splitQueries(coerceArray(value));
  120. return queries.some(mediaQuery => this._registerQuery(mediaQuery).mql.matches);
  121. }
  122. /**
  123. * Gets an observable of results for the given queries that will emit new results for any changes
  124. * in matching of the given queries.
  125. * @param value One or more media queries to check.
  126. * @returns A stream of matches for the given queries.
  127. */
  128. observe(value) {
  129. const queries = splitQueries(coerceArray(value));
  130. const observables = queries.map(query => this._registerQuery(query).observable);
  131. let stateObservable = combineLatest(observables);
  132. // Emit the first state immediately, and then debounce the subsequent emissions.
  133. stateObservable = concat(stateObservable.pipe(take(1)), stateObservable.pipe(skip(1), debounceTime(0)));
  134. return stateObservable.pipe(map(breakpointStates => {
  135. const response = {
  136. matches: false,
  137. breakpoints: {},
  138. };
  139. breakpointStates.forEach(({ matches, query }) => {
  140. response.matches = response.matches || matches;
  141. response.breakpoints[query] = matches;
  142. });
  143. return response;
  144. }));
  145. }
  146. /** Registers a specific query to be listened for. */
  147. _registerQuery(query) {
  148. // Only set up a new MediaQueryList if it is not already being listened for.
  149. if (this._queries.has(query)) {
  150. return this._queries.get(query);
  151. }
  152. const mql = this._mediaMatcher.matchMedia(query);
  153. // Create callback for match changes and add it is as a listener.
  154. const queryObservable = new Observable((observer) => {
  155. // Listener callback methods are wrapped to be placed back in ngZone. Callbacks must be placed
  156. // back into the zone because matchMedia is only included in Zone.js by loading the
  157. // webapis-media-query.js file alongside the zone.js file. Additionally, some browsers do not
  158. // have MediaQueryList inherit from EventTarget, which causes inconsistencies in how Zone.js
  159. // patches it.
  160. const handler = (e) => this._zone.run(() => observer.next(e));
  161. mql.addListener(handler);
  162. return () => {
  163. mql.removeListener(handler);
  164. };
  165. }).pipe(startWith(mql), map(({ matches }) => ({ query, matches })), takeUntil(this._destroySubject));
  166. // Add the MediaQueryList to the set of queries.
  167. const output = { observable: queryObservable, mql };
  168. this._queries.set(query, output);
  169. return output;
  170. }
  171. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: BreakpointObserver, deps: [{ token: MediaMatcher }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
  172. static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: BreakpointObserver, providedIn: 'root' }); }
  173. }
  174. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: BreakpointObserver, decorators: [{
  175. type: Injectable,
  176. args: [{ providedIn: 'root' }]
  177. }], ctorParameters: function () { return [{ type: MediaMatcher }, { type: i0.NgZone }]; } });
  178. /**
  179. * Split each query string into separate query strings if two queries are provided as comma
  180. * separated.
  181. */
  182. function splitQueries(queries) {
  183. return queries
  184. .map(query => query.split(','))
  185. .reduce((a1, a2) => a1.concat(a2))
  186. .map(query => query.trim());
  187. }
  188. // PascalCase is being used as Breakpoints is used like an enum.
  189. // tslint:disable-next-line:variable-name
  190. const Breakpoints = {
  191. XSmall: '(max-width: 599.98px)',
  192. Small: '(min-width: 600px) and (max-width: 959.98px)',
  193. Medium: '(min-width: 960px) and (max-width: 1279.98px)',
  194. Large: '(min-width: 1280px) and (max-width: 1919.98px)',
  195. XLarge: '(min-width: 1920px)',
  196. Handset: '(max-width: 599.98px) and (orientation: portrait), ' +
  197. '(max-width: 959.98px) and (orientation: landscape)',
  198. Tablet: '(min-width: 600px) and (max-width: 839.98px) and (orientation: portrait), ' +
  199. '(min-width: 960px) and (max-width: 1279.98px) and (orientation: landscape)',
  200. Web: '(min-width: 840px) and (orientation: portrait), ' +
  201. '(min-width: 1280px) and (orientation: landscape)',
  202. HandsetPortrait: '(max-width: 599.98px) and (orientation: portrait)',
  203. TabletPortrait: '(min-width: 600px) and (max-width: 839.98px) and (orientation: portrait)',
  204. WebPortrait: '(min-width: 840px) and (orientation: portrait)',
  205. HandsetLandscape: '(max-width: 959.98px) and (orientation: landscape)',
  206. TabletLandscape: '(min-width: 960px) and (max-width: 1279.98px) and (orientation: landscape)',
  207. WebLandscape: '(min-width: 1280px) and (orientation: landscape)',
  208. };
  209. /**
  210. * Generated bundle index. Do not edit.
  211. */
  212. export { BreakpointObserver, Breakpoints, LayoutModule, MediaMatcher };
  213. //# sourceMappingURL=layout.mjs.map