Skip to content

Commit f22367b

Browse files
committed
ref: css-mediaquery module refactoring
1 parent a124615 commit f22367b

File tree

8 files changed

+332
-324
lines changed

8 files changed

+332
-324
lines changed
Lines changed: 8 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,16 @@
1-
import { MediaQueryType, matchQuery, parseQuery } from '.';
1+
import { checkIfMediaQueryMatches } from '.';
2+
import { Screen } from '../platform';
23

34
describe('css-mediaquery', () => {
4-
describe('parseQuery', () => {
5-
it('should parse media queries without expressions', () => {
6-
expect(parseQuery('screen')).toEqual([
7-
{
8-
inverse: false,
9-
type: MediaQueryType.screen,
10-
features: [],
11-
},
12-
]);
5+
const { widthDIPs } = Screen.mainScreen;
136

14-
expect(parseQuery('not screen')).toEqual([
15-
{
16-
inverse: true,
17-
type: MediaQueryType.screen,
18-
features: [],
19-
},
20-
]);
7+
describe('checkIfMediaQueryMatches', () => {
8+
it('should return true for a correct match', () => {
9+
expect(checkIfMediaQueryMatches(`only screen and (max-width: ${widthDIPs})`)).toBe(true);
2110
});
2211

23-
it('should throw a SyntaxError when a media query is invalid', () => {
24-
expect(() => parseQuery('some crap')).toThrow(SyntaxError);
25-
expect(() => parseQuery('48em')).toThrow(SyntaxError);
26-
expect(() => parseQuery('screen and crap')).toThrow(SyntaxError);
27-
expect(() => parseQuery('screen and (48em)')).toThrow(SyntaxError);
28-
expect(() => parseQuery('screen and (foo:)')).toThrow(SyntaxError);
29-
expect(() => parseQuery('()')).toThrow(SyntaxError);
30-
expect(() => parseQuery('(foo) (bar)')).toThrow(SyntaxError);
31-
expect(() => parseQuery('(foo:) and (bar)')).toThrow(SyntaxError);
32-
});
33-
});
34-
35-
describe('matchQuery', () => {
36-
describe('Equality check', () => {
37-
it('orientation: should return true for a correct match (===)', () => {
38-
expect(matchQuery('(orientation: portrait)', { orientation: 'portrait' })).toBe(true);
39-
});
40-
41-
it('orientation: should return false for an incorrect match (===)', () => {
42-
expect(matchQuery('(orientation: landscape)', { orientation: 'portrait' })).toBe(false);
43-
});
44-
45-
it('prefers-color-scheme: should return true for a correct match (===)', () => {
46-
expect(matchQuery('(prefers-color-scheme: dark)', { 'prefers-color-scheme': 'dark' })).toBe(true);
47-
});
48-
49-
it('prefers-color-scheme: should return false for an incorrect match (===)', () => {
50-
expect(matchQuery('(prefers-color-scheme: light)', { 'prefers-color-scheme': 'dark' })).toBe(false);
51-
});
52-
53-
it('width: should return true for a correct match', () => {
54-
expect(matchQuery('(width: 800px)', { width: 800 })).toBe(true);
55-
});
56-
57-
it('width: should return false for an incorrect match', () => {
58-
expect(matchQuery('(width: 800px)', { width: 900 })).toBe(false);
59-
});
60-
});
61-
62-
describe('Type', () => {
63-
it('should return true for a correct match', () => {
64-
expect(matchQuery('screen', { type: MediaQueryType.screen })).toBe(true);
65-
});
66-
67-
it('should return false for an incorrect match', () => {
68-
expect(
69-
matchQuery('screen and (orientation: portrait)', {
70-
type: MediaQueryType.print,
71-
orientation: 'portrait',
72-
}),
73-
).toBe(false);
74-
});
75-
76-
it('should return false for a media query without a type when type is specified in the value object', () => {
77-
expect(matchQuery('(min-width: 500px)', { type: MediaQueryType.screen })).toBe(false);
78-
});
79-
80-
it('should return true for a media query without a type when type is not specified in the value object', () => {
81-
expect(matchQuery('(min-width: 500px)', { width: 700 })).toBe(true);
82-
});
83-
});
84-
85-
describe('Not', () => {
86-
it('should return false when theres a match on a `not` query', () => {
87-
expect(
88-
matchQuery('not screen and (orientation: portrait)', {
89-
type: MediaQueryType.screen,
90-
orientation: 'landscape',
91-
}),
92-
).toBe(false);
93-
});
94-
95-
it('should not disrupt an OR query', () => {
96-
expect(
97-
matchQuery('not screen and (color), screen and (min-height: 48em)', {
98-
type: MediaQueryType.screen,
99-
height: 1000,
100-
}),
101-
).toBe(true);
102-
});
103-
104-
it('should return false for when type === all', () => {
105-
expect(
106-
matchQuery('not all and (min-width: 48em)', {
107-
type: MediaQueryType.all,
108-
width: 1000,
109-
}),
110-
).toBe(false);
111-
});
112-
113-
it('should return true for inverted value', () => {
114-
expect(matchQuery('not screen and (min-width: 48px)', { width: 24 })).toBe(true);
115-
});
12+
it('should return false for an incorrect match', () => {
13+
expect(checkIfMediaQueryMatches(`only screen and (max-width: ${widthDIPs - 1})`)).toBe(false);
11614
});
11715
});
11816
});
Lines changed: 24 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,28 @@
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';
93
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,
11521
});
22+
} catch (err) {
23+
matches = false;
24+
Trace.write(err, Trace.categories.MediaQuery, Trace.messageType.error);
25+
}
11626

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;
17628
}
File renamed without changes.

0 commit comments

Comments
 (0)