@@ -184,11 +184,12 @@ const extractIfFunctions = (cssText) => {
184
184
} ;
185
185
186
186
/**
187
- * Parse if() function content
187
+ * Parse if() function content - supports both single conditions and multiple chained conditions
188
188
*/
189
189
const parseIfFunction = ( content ) => {
190
- // Find the colon that separates condition from values
191
- let colonIndex = - 1 ;
190
+ // Split content by semicolons, but respect parentheses and quotes
191
+ const segments = [ ] ;
192
+ let currentSegment = '' ;
192
193
let parenDepth = 0 ;
193
194
let inQuotes = false ;
194
195
let quoteChar = '' ;
@@ -213,44 +214,111 @@ const parseIfFunction = (content) => {
213
214
parenDepth ++ ;
214
215
} else if ( char === ')' ) {
215
216
parenDepth -- ;
216
- } else if ( char === ':' && parenDepth === 0 ) {
217
- colonIndex = i ;
218
- break ;
217
+ } else if ( char === ';' && parenDepth === 0 ) {
218
+ // End of segment
219
+ segments . push ( currentSegment . trim ( ) ) ;
220
+ currentSegment = '' ;
221
+ continue ;
219
222
}
220
223
}
224
+
225
+ currentSegment += char ;
221
226
}
222
227
223
- if ( colonIndex === - 1 ) {
224
- throw new Error ( 'Invalid if() function: missing colon' ) ;
228
+ // Add the last segment
229
+ if ( currentSegment . trim ( ) ) {
230
+ segments . push ( currentSegment . trim ( ) ) ;
225
231
}
226
232
227
- const condition = content . slice ( 0 , colonIndex ) . trim ( ) ;
228
- const valuesString = content . slice ( colonIndex + 1 ) . trim ( ) ;
233
+ // Parse segments into conditions and values
234
+ const conditions = [ ] ;
235
+ let elseValue = null ;
229
236
230
- // Parse values (value; else: fallback)
231
- const elseMatch = valuesString . match ( / ^ ( .* ?) ; ? \s * e l s e : \s * ( .* ) $ / ) ;
237
+ for ( const segment of segments ) {
238
+ // Check if this is an else clause
239
+ const elseMatch = segment . match ( / ^ e l s e : \s * ( .* ) $ / ) ;
240
+ if ( elseMatch ) {
241
+ elseValue = elseMatch [ 1 ] . trim ( ) ;
242
+ continue ;
243
+ }
232
244
233
- if ( ! elseMatch ) {
234
- throw new Error ( 'Invalid if() function: missing else clause' ) ;
235
- }
245
+ // Parse condition: value format
246
+ let colonIndex = - 1 ;
247
+ let parenDepth = 0 ;
248
+ let inQuotes = false ;
249
+ let quoteChar = '' ;
250
+
251
+ // Find the colon that's outside of parentheses and quotes
252
+ for ( let i = 0 ; i < segment . length ; i ++ ) {
253
+ const char = segment [ i ] ;
254
+ const previousChar = i > 0 ? segment [ i - 1 ] : '' ;
255
+
256
+ // Handle quotes
257
+ if ( ( char === '"' || char === "'" ) && previousChar !== '\\' ) {
258
+ if ( ! inQuotes ) {
259
+ inQuotes = true ;
260
+ quoteChar = char ;
261
+ } else if ( char === quoteChar ) {
262
+ inQuotes = false ;
263
+ quoteChar = '' ;
264
+ }
265
+ }
236
266
237
- const trueValue = elseMatch [ 1 ] . trim ( ) ;
238
- const falseValue = elseMatch [ 2 ] . trim ( ) ;
267
+ if ( ! inQuotes ) {
268
+ if ( char === '(' ) {
269
+ parenDepth ++ ;
270
+ } else if ( char === ')' ) {
271
+ parenDepth -- ;
272
+ } else if ( char === ':' && parenDepth === 0 ) {
273
+ colonIndex = i ;
274
+ break ;
275
+ }
276
+ }
277
+ }
239
278
240
- // Parse condition
241
- const conditionMatch = condition . match ( / ^ ( s t y l e | m e d i a | s u p p o r t s ) \( ( .* ) \) $ / ) ;
242
- if ( ! conditionMatch ) {
243
- throw new Error ( 'Invalid if() function: unknown condition type' ) ;
279
+ if ( colonIndex === - 1 ) {
280
+ throw new Error ( 'Invalid if() function: missing colon in segment' ) ;
281
+ }
282
+
283
+ const conditionPart = segment . slice ( 0 , colonIndex ) . trim ( ) ;
284
+ const valuePart = segment . slice ( colonIndex + 1 ) . trim ( ) ;
285
+
286
+ // Parse the condition type and expression
287
+ const conditionMatch = conditionPart . match (
288
+ / ^ ( s t y l e | m e d i a | s u p p o r t s ) \( ( .* ) \) $ /
289
+ ) ;
290
+ if ( ! conditionMatch ) {
291
+ throw new Error (
292
+ `Invalid if() function: unknown condition type in "${ conditionPart } "`
293
+ ) ;
294
+ }
295
+
296
+ conditions . push ( {
297
+ conditionType : conditionMatch [ 1 ] ,
298
+ conditionExpression : conditionMatch [ 2 ] ,
299
+ value : valuePart
300
+ } ) ;
244
301
}
245
302
246
- const conditionType = conditionMatch [ 1 ] ;
247
- const conditionExpression = conditionMatch [ 2 ] ;
303
+ if ( ! elseValue ) {
304
+ throw new Error ( 'Invalid if() function: missing else clause' ) ;
305
+ }
248
306
307
+ // For backward compatibility, if there's only one condition, return the old format
308
+ if ( conditions . length === 1 ) {
309
+ return {
310
+ conditionType : conditions [ 0 ] . conditionType ,
311
+ conditionExpression : conditions [ 0 ] . conditionExpression ,
312
+ trueValue : conditions [ 0 ] . value ,
313
+ falseValue : elseValue
314
+ } ;
315
+ }
316
+
317
+ // For multiple conditions, return the new format
249
318
return {
250
- conditionType,
251
- conditionExpression,
252
- trueValue,
253
- falseValue
319
+ conditions,
320
+ falseValue : elseValue ,
321
+ isMultipleConditions : true
254
322
} ;
255
323
} ;
256
324
@@ -276,41 +344,94 @@ const transformPropertyToNative = (selector, property, value) => {
276
344
try {
277
345
const parsed = parseIfFunction ( ifFunc . content ) ;
278
346
279
- if ( parsed . conditionType === 'style' ) {
280
- // Style() conditions need runtime processing
281
- runtimeRules . push ( {
282
- selector,
283
- property,
284
- value,
285
- condition : parsed
286
- } ) ;
287
- } else {
288
- // Media() and supports() can be transformed to native CSS
289
- const nativeCondition =
290
- parsed . conditionType === 'media'
291
- ? `@media (${ parsed . conditionExpression } )`
292
- : `@supports (${ parsed . conditionExpression } )` ;
293
-
294
- // Create conditional rule with true value
295
- const trueValue = value . replace (
296
- ifFunc . fullFunction ,
297
- parsed . trueValue
347
+ // Handle multiple conditions format
348
+ if ( parsed . isMultipleConditions ) {
349
+ // Check if any condition uses style() - if so, needs runtime processing
350
+ const hasStyleCondition = parsed . conditions . some (
351
+ ( condition ) => condition . conditionType === 'style'
298
352
) ;
299
- nativeRules . push ( {
300
- condition : nativeCondition ,
301
- rule : `${ selector } { ${ property } : ${ trueValue } ; }`
302
- } ) ;
303
353
304
- // Create fallback rule with false value
305
- const falseValue = value . replace (
354
+ if ( hasStyleCondition ) {
355
+ // If any condition uses style(), fall back to runtime processing
356
+ runtimeRules . push ( {
357
+ selector,
358
+ property,
359
+ value,
360
+ condition : parsed
361
+ } ) ;
362
+ continue ;
363
+ }
364
+
365
+ // All conditions are media() or supports() - can transform to native CSS
366
+ // Create fallback rule first
367
+ const fallbackValue = value . replace (
306
368
ifFunc . fullFunction ,
307
369
parsed . falseValue
308
370
) ;
309
371
nativeRules . push ( {
310
372
condition : null , // No condition = fallback
311
- rule : `${ selector } { ${ property } : ${ falseValue } ; }`
373
+ rule : `${ selector } { ${ property } : ${ fallbackValue } ; }`
312
374
} ) ;
375
+
376
+ // Create conditional rules for each condition (in reverse order for CSS cascade)
377
+ const { conditions } = parsed ;
378
+ for ( let i = conditions . length - 1 ; i >= 0 ; i -- ) {
379
+ const condition = conditions [ i ] ;
380
+ const nativeCondition =
381
+ condition . conditionType === 'media'
382
+ ? `@media (${ condition . conditionExpression } )`
383
+ : `@supports (${ condition . conditionExpression } )` ;
384
+
385
+ const conditionalValue = value . replace (
386
+ ifFunc . fullFunction ,
387
+ condition . value
388
+ ) ;
389
+ nativeRules . push ( {
390
+ condition : nativeCondition ,
391
+ rule : `${ selector } { ${ property } : ${ conditionalValue } ; }`
392
+ } ) ;
393
+ }
394
+
395
+ continue ;
396
+ }
397
+
398
+ // Handle single condition format (backward compatibility)
399
+ if ( parsed . conditionType === 'style' ) {
400
+ // Style() conditions need runtime processing
401
+ runtimeRules . push ( {
402
+ selector,
403
+ property,
404
+ value,
405
+ condition : parsed
406
+ } ) ;
407
+ continue ;
313
408
}
409
+
410
+ // Media() and supports() can be transformed to native CSS
411
+ const nativeCondition =
412
+ parsed . conditionType === 'media'
413
+ ? `@media (${ parsed . conditionExpression } )`
414
+ : `@supports (${ parsed . conditionExpression } )` ;
415
+
416
+ // Create conditional rule with true value
417
+ const trueValue = value . replace (
418
+ ifFunc . fullFunction ,
419
+ parsed . trueValue
420
+ ) ;
421
+ nativeRules . push ( {
422
+ condition : nativeCondition ,
423
+ rule : `${ selector } { ${ property } : ${ trueValue } ; }`
424
+ } ) ;
425
+
426
+ // Create fallback rule with false value
427
+ const falseValue = value . replace (
428
+ ifFunc . fullFunction ,
429
+ parsed . falseValue
430
+ ) ;
431
+ nativeRules . push ( {
432
+ condition : null , // No condition = fallback
433
+ rule : `${ selector } { ${ property } : ${ falseValue } ; }`
434
+ } ) ;
314
435
} catch ( error ) {
315
436
// If parsing fails, fall back to runtime processing
316
437
runtimeRules . push ( {
0 commit comments