breakpoints-observer-CljOfYGy.mjs 8.2 KB

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