1 | import { Observable } from '../data/observable';
|
2 | import { Screen } from '../platform';
|
3 | import { Application } from '../application';
|
4 | import { matchQuery, MediaQueryType } from '../css-mediaquery';
|
5 | import { Trace } from '../trace';
|
6 | const mediaQueryLists = [];
|
7 | const applicationEvents = [Application.orientationChangedEvent, Application.systemAppearanceChangedEvent];
|
8 |
|
9 | let isMediaInitializationEnabled = false;
|
10 | function toggleApplicationEventListeners(toAdd) {
|
11 | for (const eventName of applicationEvents) {
|
12 | if (toAdd) {
|
13 | Application.on(eventName, onDeviceChange);
|
14 | }
|
15 | else {
|
16 | Application.off(eventName, onDeviceChange);
|
17 | }
|
18 | }
|
19 | }
|
20 | function onDeviceChange(args) {
|
21 | for (const mql of mediaQueryLists) {
|
22 | const matches = checkIfMediaQueryMatches(mql.media);
|
23 | if (mql.matches !== matches) {
|
24 | mql._matches = matches;
|
25 | mql.notify({
|
26 | eventName: MediaQueryListImpl.changeEvent,
|
27 | object: mql,
|
28 | matches: mql.matches,
|
29 | media: mql.media,
|
30 | });
|
31 | }
|
32 | }
|
33 | }
|
34 | function checkIfMediaQueryMatches(mediaQueryString) {
|
35 | const { widthPixels, heightPixels } = Screen.mainScreen;
|
36 | let matches;
|
37 | try {
|
38 | matches = matchQuery(mediaQueryString, {
|
39 | type: MediaQueryType.screen,
|
40 | width: widthPixels,
|
41 | height: heightPixels,
|
42 | 'device-width': widthPixels,
|
43 | 'device-height': heightPixels,
|
44 | orientation: Application.orientation(),
|
45 | 'prefers-color-scheme': Application.systemAppearance(),
|
46 | });
|
47 | }
|
48 | catch (err) {
|
49 | matches = false;
|
50 | Trace.write(err, Trace.categories.MediaQuery, Trace.messageType.error);
|
51 | }
|
52 | return matches;
|
53 | }
|
54 | function matchMedia(mediaQueryString) {
|
55 | isMediaInitializationEnabled = true;
|
56 | const mediaQueryList = new MediaQueryListImpl();
|
57 | isMediaInitializationEnabled = false;
|
58 | mediaQueryList._media = mediaQueryString;
|
59 | mediaQueryList._matches = checkIfMediaQueryMatches(mediaQueryString);
|
60 | return mediaQueryList;
|
61 | }
|
62 | class MediaQueryListImpl extends Observable {
|
63 | constructor() {
|
64 | super();
|
65 | if (!isMediaInitializationEnabled) {
|
66 | throw new TypeError('Illegal constructor');
|
67 | }
|
68 | Object.defineProperties(this, {
|
69 | _media: {
|
70 | writable: true,
|
71 | },
|
72 | _matches: {
|
73 | writable: true,
|
74 | },
|
75 | _onChange: {
|
76 | writable: true,
|
77 | value: null,
|
78 | },
|
79 | mediaQueryChangeListeners: {
|
80 | value: new Map(),
|
81 | },
|
82 | _throwInvocationError: {
|
83 | value: null,
|
84 | },
|
85 | });
|
86 | }
|
87 | get media() {
|
88 | this._throwInvocationError?.();
|
89 | return this._media;
|
90 | }
|
91 | get matches() {
|
92 | this._throwInvocationError?.();
|
93 | return this._matches;
|
94 | }
|
95 |
|
96 | addEventListener(eventName, callback, thisArg) {
|
97 | this._throwInvocationError?.();
|
98 | const hasChangeListeners = this.hasListeners(MediaQueryListImpl.changeEvent);
|
99 |
|
100 | super.addEventListener(eventName, callback, thisArg);
|
101 | if (eventName === MediaQueryListImpl.changeEvent && !hasChangeListeners) {
|
102 | mediaQueryLists.push(this);
|
103 | if (mediaQueryLists.length === 1) {
|
104 | toggleApplicationEventListeners(true);
|
105 | }
|
106 | }
|
107 | }
|
108 |
|
109 | removeEventListener(eventName, callback, thisArg) {
|
110 | this._throwInvocationError?.();
|
111 |
|
112 | super.removeEventListener(eventName, callback, thisArg);
|
113 | if (eventName === MediaQueryListImpl.changeEvent) {
|
114 | const hasChangeListeners = this.hasListeners(MediaQueryListImpl.changeEvent);
|
115 | if (!hasChangeListeners) {
|
116 | const index = mediaQueryLists.indexOf(this);
|
117 | if (index >= 0) {
|
118 | mediaQueryLists.splice(index, 1);
|
119 | if (!mediaQueryLists.length) {
|
120 | toggleApplicationEventListeners(false);
|
121 | }
|
122 | }
|
123 | }
|
124 | }
|
125 | }
|
126 | addListener(callback) {
|
127 | this._throwInvocationError?.();
|
128 |
|
129 |
|
130 | const wrapperCallback = (data) => {
|
131 | callback.call(this, {
|
132 | matches: this.matches,
|
133 | media: this.media,
|
134 | });
|
135 | };
|
136 |
|
137 | this.addEventListener(MediaQueryListImpl.changeEvent, wrapperCallback);
|
138 | this.mediaQueryChangeListeners.set(callback, wrapperCallback);
|
139 | }
|
140 | removeListener(callback) {
|
141 | this._throwInvocationError?.();
|
142 | if (this.mediaQueryChangeListeners.has(callback)) {
|
143 |
|
144 | this.removeEventListener(MediaQueryListImpl.changeEvent, this.mediaQueryChangeListeners.get(callback));
|
145 | this.mediaQueryChangeListeners.delete(callback);
|
146 | }
|
147 | }
|
148 | get onchange() {
|
149 | this._throwInvocationError?.();
|
150 | return this._onChange;
|
151 | }
|
152 | set onchange(callback) {
|
153 | this._throwInvocationError?.();
|
154 |
|
155 | if (this._onChange) {
|
156 | this.removeListener(this._onChange);
|
157 | }
|
158 | if (callback) {
|
159 | this.addListener(callback);
|
160 | }
|
161 | this._onChange = callback;
|
162 | }
|
163 | _throwInvocationError() {
|
164 | throw new TypeError('Illegal invocation');
|
165 | }
|
166 | }
|
167 | MediaQueryListImpl.changeEvent = 'change';
|
168 | export { matchMedia, MediaQueryListImpl as MediaQueryList, checkIfMediaQueryMatches };
|
169 |
|
\ | No newline at end of file |