@@ -32,6 +32,12 @@ function* ParseInt() {
32
32
return parseInt ( stringValue , 10 ) * ( isNegative ? - 1 : 1 ) ;
33
33
}
34
34
35
+ interface MatchMediaContext {
36
+ mediaType : 'screen' | 'print' ;
37
+ viewportWidth : number ;
38
+ viewportHeight : number ;
39
+ }
40
+
35
41
class ParsedMediaType {
36
42
constructor ( public readonly mediaType : 'screen' | 'print' | 'all' ) { }
37
43
@@ -42,11 +48,32 @@ class ParsedMediaType {
42
48
43
49
static * Parser ( ) {
44
50
yield optionalWhitespace ;
51
+ yield optional ( ( ) => [ 'only' , requiredWhitespace ] ) ;
45
52
const mediaType : ParsedMediaType [ 'mediaType' ] = yield [ 'screen' , 'print' ] ;
46
53
return new ParsedMediaType ( mediaType ) ;
47
54
}
48
55
}
49
56
57
+ class ParsedNotMediaType {
58
+ constructor ( public readonly mediaType : 'screen' | 'print' | 'all' ) { }
59
+
60
+ matches ( context : { mediaType : 'screen' | 'print' } ) {
61
+ if ( this . mediaType === 'all' ) return false ;
62
+ return this . mediaType !== context . mediaType ;
63
+ }
64
+
65
+ static * Parser ( ) {
66
+ yield optionalWhitespace ;
67
+ yield 'not' ;
68
+ yield requiredWhitespace ;
69
+ const mediaType : ParsedNotMediaType [ 'mediaType' ] = yield [
70
+ 'screen' ,
71
+ 'print' ,
72
+ ] ;
73
+ return new ParsedNotMediaType ( mediaType ) ;
74
+ }
75
+ }
76
+
50
77
class ParsedMinWidth {
51
78
constructor (
52
79
public readonly value : number ,
@@ -72,18 +99,48 @@ class ParsedMinWidth {
72
99
}
73
100
}
74
101
102
+ /**
103
+ https://www.w3.org/TR/mediaqueries-5/#orientation
104
+ */
105
+ class ParsedOrientation {
106
+ constructor ( public readonly orientation : 'portrait' | 'landscape' ) { }
107
+
108
+ matches ( context : { viewportWidth : number ; viewportHeight : number } ) {
109
+ const calculated =
110
+ context . viewportHeight >= context . viewportWidth
111
+ ? 'portrait'
112
+ : 'landscape' ;
113
+ return this . orientation === calculated ;
114
+ }
115
+
116
+ static * Parser ( ) {
117
+ yield optionalWhitespace ;
118
+ yield '(' ;
119
+ yield 'orientation:' ;
120
+ yield optionalWhitespace ;
121
+ const orientation : 'portrait' | 'landscape' = yield [
122
+ 'portrait' ,
123
+ 'landscape' ,
124
+ ] ;
125
+ yield optionalWhitespace ;
126
+ yield ')' ;
127
+ return new ParsedOrientation ( orientation ) ;
128
+ }
129
+ }
130
+
75
131
// See https://www.w3.org/TR/mediaqueries-5/#mq-syntax
76
- type ParsedMediaFeature = ParsedMinWidth ;
132
+ const parsedMediaFeature = [ ParsedMinWidth . Parser , ParsedOrientation . Parser ] ;
133
+ const parsedMediaInParens = [ ...parsedMediaFeature ] ;
134
+ type ParsedMediaFeature = ParsedType < typeof parsedMediaFeature [ - 1 ] > ;
77
135
type ParsedMediaInParens = ParsedMediaFeature ;
78
- const parsedMediaInParens = [ ParsedMinWidth . Parser ] ;
79
136
80
137
class ParsedMediaCondition {
81
138
constructor (
82
139
public readonly first : ParsedMediaFeature ,
83
140
public readonly conditions ?: ParsedMediaAnds
84
141
) { }
85
142
86
- matches ( context : { mediaType : 'screen' | 'print' ; viewportWidth : number } ) {
143
+ matches ( context : MatchMediaContext ) {
87
144
const base = this . first . matches ( context ) ;
88
145
if ( this . conditions ) {
89
146
return base && this . conditions . matches ( context ) ;
@@ -94,7 +151,7 @@ class ParsedMediaCondition {
94
151
95
152
static * Parser ( ) {
96
153
yield optionalWhitespace ;
97
- const first : ParsedMediaFeature = yield [ ParsedMinWidth . Parser ] ;
154
+ const first : ParsedMediaInParens = yield parsedMediaInParens ;
98
155
// const conditions: ParsedMediaAnds | undefined = yield optional(ParsedMediaAnds.Parser);
99
156
const conditions : ParsedMediaAnds | '' = yield [ ParsedMediaAnds . Parser , '' ] ;
100
157
if ( conditions === '' ) {
@@ -108,7 +165,7 @@ class ParsedMediaCondition {
108
165
class ParsedMediaAnds {
109
166
constructor ( public readonly list : ReadonlyArray < ParsedMediaInParens > ) { }
110
167
111
- matches ( context : { mediaType : 'screen' | 'print' ; viewportWidth : number } ) {
168
+ matches ( context : MatchMediaContext ) {
112
169
return this . list . every ( ( m ) => m . matches ( context ) ) ;
113
170
}
114
171
@@ -130,19 +187,22 @@ class ParsedMediaAnds {
130
187
131
188
class ParsedMediaTypeThenConditionWithoutOr {
132
189
constructor (
133
- public readonly mediaType : ParsedMediaType ,
190
+ public readonly mediaType : ParsedMediaType | ParsedNotMediaType ,
134
191
public readonly and : ReadonlyArray < ParsedMediaInParens >
135
192
) { }
136
193
137
- matches ( context : { mediaType : 'screen' | 'print' ; viewportWidth : number } ) {
194
+ matches ( context : MatchMediaContext ) {
138
195
return (
139
196
this . mediaType . matches ( context ) &&
140
197
this . and . every ( ( m ) => m . matches ( context ) )
141
198
) ;
142
199
}
143
200
144
201
static * Parser ( ) {
145
- const mediaType : ParsedMediaType = yield ParsedMediaType . Parser ;
202
+ const mediaType : ParsedMediaType | ParsedNotMediaType = yield [
203
+ ParsedMediaType . Parser ,
204
+ ParsedNotMediaType . Parser ,
205
+ ] ;
146
206
147
207
const list : Array < ParsedMediaInParens > = [ ] ;
148
208
@@ -176,10 +236,6 @@ class ParsedMediaQuery {
176
236
}
177
237
}
178
238
179
- interface MatchMediaContext {
180
- mediaType : 'screen' | 'print' ;
181
- viewportWidth : number ;
182
- }
183
239
function matchMedia ( context : MatchMediaContext , mediaQuery : string ) {
184
240
const parsed : ParseResult < ParsedMediaQuery > = parse (
185
241
mediaQuery ,
@@ -202,6 +258,15 @@ function matchMedia(context: MatchMediaContext, mediaQuery: string) {
202
258
} ;
203
259
}
204
260
261
+ test ( 'screen' , ( ) => {
262
+ const result = parse ( 'screen' , ParsedMediaQuery . Parser ( ) as any ) ;
263
+ expect ( result ) . toEqual ( {
264
+ success : true ,
265
+ result : new ParsedMediaType ( 'screen' ) ,
266
+ remaining : '' ,
267
+ } ) ;
268
+ } ) ;
269
+
205
270
test ( '(min-width: 480px)' , ( ) => {
206
271
const result = parse ( '(min-width: 480px)' , ParsedMediaQuery . Parser ( ) as any ) ;
207
272
expect ( result ) . toEqual ( {
@@ -211,11 +276,14 @@ test('(min-width: 480px)', () => {
211
276
} ) ;
212
277
} ) ;
213
278
214
- test ( 'screen' , ( ) => {
215
- const result = parse ( 'screen' , ParsedMediaQuery . Parser ( ) as any ) ;
279
+ test ( '(orientation: landscape)' , ( ) => {
280
+ const result = parse (
281
+ '(orientation: landscape)' ,
282
+ ParsedMediaQuery . Parser ( ) as any
283
+ ) ;
216
284
expect ( result ) . toEqual ( {
217
285
success : true ,
218
- result : new ParsedMediaType ( 'screen ') ,
286
+ result : new ParsedOrientation ( 'landscape ') ,
219
287
remaining : '' ,
220
288
} ) ;
221
289
} ) ;
@@ -238,17 +306,19 @@ test('screen and (min-width: 480px)', () => {
238
306
test ( 'matchMedia()' , ( ) => {
239
307
const screenSized = ( viewportWidth : number , viewportHeight : number ) =>
240
308
( { mediaType : 'screen' , viewportWidth, viewportHeight } as const ) ;
241
- const print = { mediaType : 'print' } as const ;
309
+ const printSized = ( viewportWidth : number , viewportHeight : number ) =>
310
+ ( { mediaType : 'print' , viewportWidth, viewportHeight } as const ) ;
242
311
243
312
expect ( matchMedia ( screenSized ( 100 , 100 ) , 'screen' ) . matches ) . toBe ( true ) ;
313
+ expect ( matchMedia ( screenSized ( 100 , 100 ) , 'only screen' ) . matches ) . toBe ( true ) ;
314
+ expect ( matchMedia ( screenSized ( 100 , 100 ) , 'not screen' ) . matches ) . toBe ( false ) ;
244
315
expect ( matchMedia ( screenSized ( 100 , 100 ) , 'print' ) . matches ) . toBe ( false ) ;
316
+ expect ( matchMedia ( screenSized ( 100 , 100 ) , 'only print' ) . matches ) . toBe ( false ) ;
245
317
246
- expect ( matchMedia ( { ...print , viewportWidth : 100 } , 'screen' ) . matches ) . toBe (
247
- false
248
- ) ;
249
- expect ( matchMedia ( { ...print , viewportWidth : 100 } , 'print' ) . matches ) . toBe (
250
- true
251
- ) ;
318
+ expect ( matchMedia ( printSized ( 100 , 100 ) , 'screen' ) . matches ) . toBe ( false ) ;
319
+ expect ( matchMedia ( printSized ( 100 , 100 ) , 'only screen' ) . matches ) . toBe ( false ) ;
320
+ expect ( matchMedia ( printSized ( 100 , 100 ) , 'print' ) . matches ) . toBe ( true ) ;
321
+ expect ( matchMedia ( printSized ( 100 , 100 ) , 'only print' ) . matches ) . toBe ( true ) ;
252
322
253
323
expect ( matchMedia ( screenSized ( 478 , 100 ) , '(min-width: 480px)' ) . matches ) . toBe (
254
324
false
@@ -262,4 +332,37 @@ test('matchMedia()', () => {
262
332
expect ( matchMedia ( screenSized ( 481 , 100 ) , '(min-width: 480px)' ) . matches ) . toBe (
263
333
true
264
334
) ;
335
+
336
+ expect (
337
+ matchMedia ( screenSized ( 200 , 100 ) , '(orientation: landscape)' ) . matches
338
+ ) . toBe ( true ) ;
339
+ expect (
340
+ matchMedia ( screenSized ( 200 , 100 ) , '(orientation: portrait)' ) . matches
341
+ ) . toBe ( false ) ;
342
+
343
+ expect (
344
+ matchMedia ( screenSized ( 100 , 200 ) , '(orientation: landscape)' ) . matches
345
+ ) . toBe ( false ) ;
346
+ expect (
347
+ matchMedia ( screenSized ( 100 , 200 ) , '(orientation: portrait)' ) . matches
348
+ ) . toBe ( true ) ;
349
+
350
+ expect (
351
+ matchMedia ( screenSized ( 100 , 100 ) , '(orientation: landscape)' ) . matches
352
+ ) . toBe ( false ) ;
353
+ expect (
354
+ matchMedia ( screenSized ( 100 , 100 ) , '(orientation: portrait)' ) . matches
355
+ ) . toBe ( true ) ;
356
+
357
+ expect (
358
+ matchMedia ( screenSized ( 481 , 100 ) , 'screen and (min-width: 480px)' ) . matches
359
+ ) . toBe ( true ) ;
360
+ expect (
361
+ matchMedia ( screenSized ( 481 , 100 ) , 'only screen and (min-width: 480px)' )
362
+ . matches
363
+ ) . toBe ( true ) ;
364
+ expect (
365
+ matchMedia ( screenSized ( 481 , 100 ) , 'only screen and (min-width: 480px) and (orientation: landscape)' )
366
+ . matches
367
+ ) . toBe ( true ) ;
265
368
} ) ;
0 commit comments