@@ -12,7 +12,12 @@ import isPlainObject from './util/isPlainObject'
12
12
import transformThemeValue from './util/transformThemeValue'
13
13
import { version as tailwindVersion } from '../package.json'
14
14
import log from './util/log'
15
- import { normalizeScreens } from './util/normalizeScreens'
15
+ import {
16
+ normalizeScreens ,
17
+ isScreenSortable ,
18
+ compareScreens ,
19
+ toScreen ,
20
+ } from './util/normalizeScreens'
16
21
import { formatBoxShadowValue , parseBoxShadowValue } from './util/parseBoxShadowValue'
17
22
import { removeAlphaVariables } from './util/removeAlphaVariables'
18
23
import { flagEnabled } from './featureFlags'
@@ -220,12 +225,131 @@ export let variantPlugins = {
220
225
addVariant ( 'print' , '@media print' )
221
226
} ,
222
227
223
- screenVariants : ( { theme, addVariant } ) => {
224
- for ( let screen of normalizeScreens ( theme ( 'screens' ) ) ) {
225
- let query = buildMediaQuery ( screen )
228
+ screenVariants : ( { theme, addVariant, matchVariant } ) => {
229
+ let rawScreens = theme ( 'screens' ) ?? { }
230
+ let areSimpleScreens = Object . values ( rawScreens ) . every ( ( v ) => typeof v === 'string' )
231
+ let screens = normalizeScreens ( theme ( 'screens' ) )
226
232
227
- addVariant ( screen . name , `@media ${ query } ` )
233
+ /** @type {Set<string> } */
234
+ let unitCache = new Set ( [ ] )
235
+
236
+ /** @param {string } value */
237
+ function units ( value ) {
238
+ return value . match ( / ( \D + ) $ / ) ?. [ 1 ] ?? '(none)'
239
+ }
240
+
241
+ /** @param {string } value */
242
+ function recordUnits ( value ) {
243
+ if ( value !== undefined ) {
244
+ unitCache . add ( units ( value ) )
245
+ }
246
+ }
247
+
248
+ /** @param {string } value */
249
+ function canUseUnits ( value ) {
250
+ recordUnits ( value )
251
+
252
+ // If the cache was empty it'll become 1 because we've just added the current unit
253
+ // If the cache was not empty and the units are the same the size doesn't change
254
+ // Otherwise, if the units are different from what is already known the size will always be > 1
255
+ return unitCache . size === 1
256
+ }
257
+
258
+ for ( const screen of screens ) {
259
+ for ( const value of screen . values ) {
260
+ recordUnits ( value . min )
261
+ recordUnits ( value . max )
262
+ }
263
+ }
264
+
265
+ let screensUseConsistentUnits = unitCache . size <= 1
266
+
267
+ /**
268
+ * @typedef {import('./util/normalizeScreens').Screen } Screen
269
+ */
270
+
271
+ /**
272
+ * @param {'min' | 'max' } type
273
+ * @returns {Record<string, Screen> }
274
+ */
275
+ function buildScreenValues ( type ) {
276
+ return Object . fromEntries (
277
+ screens
278
+ . filter ( ( screen ) => isScreenSortable ( screen ) . result )
279
+ . map ( ( screen ) => {
280
+ let { min, max } = screen . values [ 0 ]
281
+
282
+ if ( type === 'min' && min !== undefined ) {
283
+ return screen
284
+ } else if ( type === 'min' && max !== undefined ) {
285
+ return { ...screen , not : ! screen . not }
286
+ } else if ( type === 'max' && max !== undefined ) {
287
+ return screen
288
+ } else if ( type === 'max' && min !== undefined ) {
289
+ return { ...screen , not : ! screen . not }
290
+ }
291
+ } )
292
+ . map ( ( screen ) => [ screen . name , screen ] )
293
+ )
294
+ }
295
+
296
+ /**
297
+ * @param {'min' | 'max' } type
298
+ * @returns {(a: { value: string | Screen }, z: { value: string | Screen }) => number }
299
+ */
300
+ function buildSort ( type ) {
301
+ return ( a , z ) => compareScreens ( type , a . value , z . value )
302
+ }
303
+
304
+ let maxSort = buildSort ( 'max' )
305
+ let minSort = buildSort ( 'min' )
306
+
307
+ /** @param {'min'|'max' } type */
308
+ function buildScreenVariant ( type ) {
309
+ return ( value ) => {
310
+ if ( ! areSimpleScreens ) {
311
+ log . warn ( 'complex-screen-config' , [
312
+ 'The min and max variants are not supported with a screen configuration containing objects.' ,
313
+ ] )
314
+
315
+ return [ ]
316
+ } else if ( ! screensUseConsistentUnits ) {
317
+ log . warn ( 'mixed-screen-units' , [
318
+ 'The min and max variants are not supported with a screen configuration containing mixed units.' ,
319
+ ] )
320
+
321
+ return [ ]
322
+ } else if ( typeof value === 'string' && ! canUseUnits ( value ) ) {
323
+ log . warn ( 'minmax-have-mixed-units' , [
324
+ 'The min and max variants are not supported with a screen configuration containing mixed units.' ,
325
+ ] )
326
+
327
+ return [ ]
328
+ }
329
+
330
+ return [ `@media ${ buildMediaQuery ( toScreen ( value , type ) ) } ` ]
331
+ }
228
332
}
333
+
334
+ matchVariant ( 'max' , buildScreenVariant ( 'max' ) , {
335
+ sort : maxSort ,
336
+ values : areSimpleScreens ? buildScreenValues ( 'max' ) : { } ,
337
+ } )
338
+
339
+ // screens and min-* are sorted together when they can be
340
+ let id = 'min-screens'
341
+ for ( let screen of screens ) {
342
+ addVariant ( screen . name , `@media ${ buildMediaQuery ( screen ) } ` , {
343
+ id,
344
+ sort : areSimpleScreens && screensUseConsistentUnits ? minSort : undefined ,
345
+ value : screen ,
346
+ } )
347
+ }
348
+
349
+ matchVariant ( 'min' , buildScreenVariant ( 'min' ) , {
350
+ id,
351
+ sort : minSort ,
352
+ } )
229
353
} ,
230
354
231
355
supportsVariants : ( { matchVariant, theme } ) => {
0 commit comments