|
1 | | -/* |
2 | | -Copyright (c) 2014, Yahoo! Inc. All rights reserved. |
3 | | -Copyrights licensed under the New BSD License. |
4 | | -See the accompanying LICENSE file for terms. |
5 | | -*/ |
6 | | - |
7 | | -// https://github.com/ericf/css-mediaquery |
8 | | - |
| 1 | +import { getApplicationProperties } from '../application/helpers-common'; |
| 2 | +import { Screen } from '../platform'; |
9 | 3 | import { Trace } from '../trace'; |
10 | | -import { Length } from '../ui/styling/length-shared'; |
11 | | - |
12 | | -// ----------------------------------------------------------------------------- |
13 | | - |
14 | | -const RE_MEDIA_QUERY = /^(?:(only|not)?\s*([_a-z][_a-z0-9-]*)|(\([^\)]+\)))(?:\s*and\s*(.*))?$/i, |
15 | | - RE_MQ_EXPRESSION = /^\(\s*([_a-z-][_a-z0-9-]*)\s*(?:\:\s*([^\)]+))?\s*\)$/, |
16 | | - RE_MQ_FEATURE = /^(?:(min|max)-)?(.+)/, |
17 | | - RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?\s*$/, |
18 | | - RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/; |
19 | | - |
20 | | -export enum MediaQueryType { |
21 | | - all = 'all', |
22 | | - print = 'print', |
23 | | - screen = 'screen', |
24 | | -} |
25 | | - |
26 | | -export type MediaQueryProperties = 'width' | 'height' | 'device-width' | 'device-height' | 'orientation' | 'prefers-color-scheme'; |
27 | | - |
28 | | -export interface MediaQueryEnvironmentParams { |
29 | | - type?: MediaQueryType; |
30 | | - width?: number; |
31 | | - height?: number; |
32 | | - 'device-width'?: number; |
33 | | - 'device-height'?: number; |
34 | | - orientation?: string; |
35 | | - 'prefers-color-scheme'?: string; |
36 | | -} |
37 | | - |
38 | | -export interface MediaQueryExpression { |
39 | | - inverse: boolean; |
40 | | - type: MediaQueryType; |
41 | | - features: MediaQueryFeature[]; |
42 | | -} |
43 | | - |
44 | | -export interface MediaQueryFeature { |
45 | | - modifier: string; |
46 | | - property: MediaQueryProperties | string; |
47 | | - value: string; |
48 | | -} |
49 | | - |
50 | | -export function matchQuery(mediaQuery: string, values: MediaQueryEnvironmentParams): boolean { |
51 | | - const expressions = parseQuery(mediaQuery); |
52 | | - |
53 | | - return expressions.some((query) => { |
54 | | - const { type, inverse, features } = query; |
55 | | - |
56 | | - // Either the parsed or specified `type` is "all", or the types must be |
57 | | - // equal for a match. |
58 | | - const typeMatch = query.type === 'all' || values.type === query.type; |
59 | | - |
60 | | - // Quit early when `type` doesn't match, but take "not" into account |
61 | | - if ((typeMatch && inverse) || !(typeMatch || inverse)) { |
62 | | - return false; |
63 | | - } |
64 | | - |
65 | | - const expressionsMatch = features.every((feature) => { |
66 | | - const value: any = values[feature.property]; |
67 | | - |
68 | | - // Missing or falsy values don't match |
69 | | - if (!value && value !== 0) { |
70 | | - return false; |
71 | | - } |
72 | | - |
73 | | - switch (feature.property) { |
74 | | - case 'orientation': |
75 | | - case 'prefers-color-scheme': |
76 | | - if (typeof value !== 'string') { |
77 | | - return false; |
78 | | - } |
79 | | - |
80 | | - return value.toLowerCase() === feature.value.toLowerCase(); |
81 | | - default: { |
82 | | - // Numeric properties |
83 | | - let numVal: number; |
84 | | - |
85 | | - if (typeof value !== 'number') { |
86 | | - Trace.write(`Unknown CSS media query feature property: '${feature.property}' on '${query}'`, Trace.categories.MediaQuery, Trace.messageType.warn); |
87 | | - return false; |
88 | | - } |
89 | | - |
90 | | - switch (feature.property) { |
91 | | - case 'width': |
92 | | - case 'height': |
93 | | - case 'device-width': |
94 | | - case 'device-height': { |
95 | | - numVal = Length.toDevicePixels(Length.parse(feature.value), 0); |
96 | | - break; |
97 | | - } |
98 | | - default: |
99 | | - Trace.write(`Unknown CSS media query feature property: '${feature.property}' on '${query}'`, Trace.categories.MediaQuery, Trace.messageType.warn); |
100 | | - break; |
101 | | - } |
102 | | - |
103 | | - switch (feature.modifier) { |
104 | | - case 'min': |
105 | | - return value >= numVal; |
106 | | - case 'max': |
107 | | - return value <= numVal; |
108 | | - default: |
109 | | - return value === numVal; |
110 | | - } |
111 | | - |
112 | | - break; |
113 | | - } |
114 | | - } |
| 4 | +import { matchQuery, MediaQueryType } from './parser'; |
| 5 | + |
| 6 | +export function checkIfMediaQueryMatches(mediaQueryString: string): boolean { |
| 7 | + const { widthPixels, heightPixels } = Screen.mainScreen; |
| 8 | + |
| 9 | + let matches: boolean; |
| 10 | + |
| 11 | + try { |
| 12 | + const appProperties = getApplicationProperties(); |
| 13 | + matches = matchQuery(mediaQueryString, { |
| 14 | + type: MediaQueryType.screen, |
| 15 | + width: widthPixels, |
| 16 | + height: heightPixels, |
| 17 | + 'device-width': widthPixels, |
| 18 | + 'device-height': heightPixels, |
| 19 | + orientation: appProperties.orientation, |
| 20 | + 'prefers-color-scheme': appProperties.systemAppearance, |
115 | 21 | }); |
| 22 | + } catch (err) { |
| 23 | + matches = false; |
| 24 | + Trace.write(err, Trace.categories.MediaQuery, Trace.messageType.error); |
| 25 | + } |
116 | 26 |
|
117 | | - return (expressionsMatch && !inverse) || (!expressionsMatch && inverse); |
118 | | - }); |
119 | | -} |
120 | | - |
121 | | -export function parseQuery(mediaQuery: string): MediaQueryExpression[] { |
122 | | - const mediaQueryStrings = mediaQuery.split(','); |
123 | | - |
124 | | - return mediaQueryStrings.map((query) => { |
125 | | - query = query.trim(); |
126 | | - |
127 | | - const captures = query.match(RE_MEDIA_QUERY); |
128 | | - |
129 | | - // Media query must be valid |
130 | | - if (!captures) { |
131 | | - throw new SyntaxError(`Invalid CSS media query: '${query}'`); |
132 | | - } |
133 | | - |
134 | | - const modifier = captures[1]; |
135 | | - const type = captures[2]; |
136 | | - const featureString = ((captures[3] || '') + (captures[4] || '')).trim(); |
137 | | - |
138 | | - const expression: MediaQueryExpression = { |
139 | | - inverse: !!modifier && modifier.toLowerCase() === 'not', |
140 | | - type: MediaQueryType[type ? type.toLowerCase() : 'all'] ?? 'all', |
141 | | - features: [], |
142 | | - }; |
143 | | - |
144 | | - // Check for media query features |
145 | | - if (!featureString) { |
146 | | - return expression; |
147 | | - } |
148 | | - |
149 | | - // Split features string into a list |
150 | | - const features = featureString.match(/\([^\)]+\)/g); |
151 | | - |
152 | | - // Media query must be valid |
153 | | - if (!features) { |
154 | | - throw new SyntaxError(`Invalid CSS media query features: '${featureString}' on '${query}'`); |
155 | | - } |
156 | | - |
157 | | - for (const feature of features) { |
158 | | - const captures = feature.match(RE_MQ_EXPRESSION); |
159 | | - |
160 | | - // Media query must be valid |
161 | | - if (!captures) { |
162 | | - throw new SyntaxError(`Invalid CSS media query feature: '${feature}' on '${query}'`); |
163 | | - } |
164 | | - |
165 | | - const featureData = captures[1].toLowerCase().match(RE_MQ_FEATURE); |
166 | | - |
167 | | - expression.features.push({ |
168 | | - modifier: featureData[1], |
169 | | - property: featureData[2], |
170 | | - value: captures[2], |
171 | | - }); |
172 | | - } |
173 | | - |
174 | | - return expression; |
175 | | - }); |
| 27 | + return matches; |
176 | 28 | } |
0 commit comments