1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import { Trace } from '../trace';
|
8 | import { Length } from '../ui/styling/style-properties';
|
9 |
|
10 | const RE_MEDIA_QUERY = /^(?:(only|not)?\s*([_a-z][_a-z0-9-]*)|(\([^\)]+\)))(?:\s*and\s*(.*))?$/i, RE_MQ_EXPRESSION = /^\(\s*([_a-z-][_a-z0-9-]*)\s*(?:\:\s*([^\)]+))?\s*\)$/, RE_MQ_FEATURE = /^(?:(min|max)-)?(.+)/, RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?\s*$/, RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/;
|
11 | export var MediaQueryType;
|
12 | (function (MediaQueryType) {
|
13 | MediaQueryType["all"] = "all";
|
14 | MediaQueryType["print"] = "print";
|
15 | MediaQueryType["screen"] = "screen";
|
16 | })(MediaQueryType || (MediaQueryType = {}));
|
17 | export function matchQuery(mediaQuery, values) {
|
18 | const expressions = parseQuery(mediaQuery);
|
19 | return expressions.some((query) => {
|
20 | const { type, inverse, features } = query;
|
21 |
|
22 |
|
23 | const typeMatch = query.type === 'all' || values.type === query.type;
|
24 |
|
25 | if ((typeMatch && inverse) || !(typeMatch || inverse)) {
|
26 | return false;
|
27 | }
|
28 | const expressionsMatch = features.every((feature) => {
|
29 | const value = values[feature.property];
|
30 |
|
31 | if (!value && value !== 0) {
|
32 | return false;
|
33 | }
|
34 | switch (feature.property) {
|
35 | case 'orientation':
|
36 | case 'prefers-color-scheme':
|
37 | if (typeof value !== 'string') {
|
38 | return false;
|
39 | }
|
40 | return value.toLowerCase() === feature.value.toLowerCase();
|
41 | default: {
|
42 |
|
43 | let numVal;
|
44 | if (typeof value !== 'number') {
|
45 | Trace.write(`Unknown CSS media query feature property: '${feature.property}' on '${query}'`, Trace.categories.MediaQuery, Trace.messageType.warn);
|
46 | return false;
|
47 | }
|
48 | switch (feature.property) {
|
49 | case 'width':
|
50 | case 'height':
|
51 | case 'device-width':
|
52 | case 'device-height': {
|
53 | numVal = Length.toDevicePixels(Length.parse(feature.value), 0);
|
54 | break;
|
55 | }
|
56 | default:
|
57 | Trace.write(`Unknown CSS media query feature property: '${feature.property}' on '${query}'`, Trace.categories.MediaQuery, Trace.messageType.warn);
|
58 | break;
|
59 | }
|
60 | switch (feature.modifier) {
|
61 | case 'min':
|
62 | return value >= numVal;
|
63 | case 'max':
|
64 | return value <= numVal;
|
65 | default:
|
66 | return value === numVal;
|
67 | }
|
68 | break;
|
69 | }
|
70 | }
|
71 | });
|
72 | return (expressionsMatch && !inverse) || (!expressionsMatch && inverse);
|
73 | });
|
74 | }
|
75 | export function parseQuery(mediaQuery) {
|
76 | const mediaQueryStrings = mediaQuery.split(',');
|
77 | return mediaQueryStrings.map((query) => {
|
78 | query = query.trim();
|
79 | const captures = query.match(RE_MEDIA_QUERY);
|
80 |
|
81 | if (!captures) {
|
82 | throw new SyntaxError(`Invalid CSS media query: '${query}'`);
|
83 | }
|
84 | const modifier = captures[1];
|
85 | const type = captures[2];
|
86 | const featureString = ((captures[3] || '') + (captures[4] || '')).trim();
|
87 | const expression = {
|
88 | inverse: !!modifier && modifier.toLowerCase() === 'not',
|
89 | type: MediaQueryType[type ? type.toLowerCase() : 'all'] ?? 'all',
|
90 | features: [],
|
91 | };
|
92 |
|
93 | if (!featureString) {
|
94 | return expression;
|
95 | }
|
96 |
|
97 | const features = featureString.match(/\([^\)]+\)/g);
|
98 |
|
99 | if (!features) {
|
100 | throw new SyntaxError(`Invalid CSS media query features: '${featureString}' on '${query}'`);
|
101 | }
|
102 | for (const feature of features) {
|
103 | const captures = feature.match(RE_MQ_EXPRESSION);
|
104 |
|
105 | if (!captures) {
|
106 | throw new SyntaxError(`Invalid CSS media query feature: '${feature}' on '${query}'`);
|
107 | }
|
108 | const featureData = captures[1].toLowerCase().match(RE_MQ_FEATURE);
|
109 | expression.features.push({
|
110 | modifier: featureData[1],
|
111 | property: featureData[2],
|
112 | value: captures[2],
|
113 | });
|
114 | }
|
115 | return expression;
|
116 | });
|
117 | }
|
118 |
|
\ | No newline at end of file |