@@ -254,16 +254,67 @@ pub fn css_to_style(
254254
255255 if input. contains ( '{' ) {
256256 while let Some ( start) = input. find ( '{' ) {
257- let rest = & input[ start + 1 ..] ;
257+ // Check if there are properties before the selector
258+ let before_brace = & input[ ..start] . trim ( ) ;
259+
260+ // Split by semicolon to find the last part which should be the selector
261+ let parts: Vec < & str > = before_brace. split ( ';' ) . map ( |s| s. trim ( ) ) . collect ( ) ;
262+
263+ // Find the selector part (the last part that doesn't contain ':')
264+ // or if all parts contain ':', then the last part is the selector
265+ let ( plain_props, selector_part) = if parts. len ( ) > 1 {
266+ // Check if any part doesn't contain ':' (which would be a selector)
267+ let mut selector_idx = parts. len ( ) ;
268+ for ( i, part) in parts. iter ( ) . enumerate ( ) . rev ( ) {
269+ if !part. contains ( ':' ) || part. starts_with ( '&' ) || part. starts_with ( '@' ) {
270+ selector_idx = i;
271+ break ;
272+ }
273+ }
258274
259- let end = if selector. is_none ( ) {
260- rest. rfind ( '}' ) . unwrap ( )
275+ if selector_idx < parts. len ( ) {
276+ let ( props, sel) = parts. split_at ( selector_idx) ;
277+ ( props. join ( ";" ) , sel. join ( ";" ) )
278+ } else {
279+ // All parts contain ':', so treat the last one as selector
280+ let ( props, sel) = parts. split_at ( parts. len ( ) - 1 ) ;
281+ ( props. join ( ";" ) , sel. join ( ";" ) )
282+ }
261283 } else {
262- rest . find ( '}' ) . unwrap ( )
284+ ( "" . to_string ( ) , before_brace . to_string ( ) )
263285 } ;
286+
287+ // Process plain properties if any
288+ if !plain_props. is_empty ( ) {
289+ styles. extend ( css_to_style_block ( & plain_props, level, selector) ) ;
290+ }
291+
292+ let rest = & input[ start + 1 ..] ;
293+
294+ // Find the matching closing brace by counting braces
295+ let mut brace_count = 1 ;
296+ let mut end = 0 ;
297+ for ( i, ch) in rest. char_indices ( ) {
298+ match ch {
299+ '{' => brace_count += 1 ,
300+ '}' => {
301+ brace_count -= 1 ;
302+ if brace_count == 0 {
303+ end = i;
304+ break ;
305+ }
306+ }
307+ _ => { }
308+ }
309+ }
310+
311+ // If we didn't find a matching brace, use the first '}' as fallback
312+ if brace_count > 0 {
313+ end = rest. find ( '}' ) . unwrap_or ( rest. len ( ) ) ;
314+ }
264315 let block = & rest[ ..end] ;
265316 let sel = & if let Some ( StyleSelector :: Media { query, .. } ) = selector {
266- let local_sel = input [ ..start ] . trim ( ) . to_string ( ) ;
317+ let local_sel = selector_part . trim ( ) . to_string ( ) ;
267318 Some ( StyleSelector :: Media {
268319 query : query. clone ( ) ,
269320 selector : if local_sel == "&" {
@@ -273,13 +324,15 @@ pub fn css_to_style(
273324 } ,
274325 } )
275326 } else {
276- let sel = input [ ..start ] . trim ( ) . to_string ( ) ;
327+ let sel = selector_part . trim ( ) . to_string ( ) ;
277328 if sel. starts_with ( "@media" ) {
278329 Some ( StyleSelector :: Media {
279330 query : sel. replace ( " " , "" ) . replace ( "and(" , "and (" ) [ "@media" . len ( ) ..]
280331 . to_string ( ) ,
281332 selector : None ,
282333 } )
334+ } else if sel. is_empty ( ) {
335+ selector. clone ( )
283336 } else {
284337 Some ( StyleSelector :: Selector ( sel) )
285338 }
@@ -289,10 +342,34 @@ pub fn css_to_style(
289342 } else {
290343 css_to_style_block ( block, level, sel)
291344 } ;
292- let input_end = input. rfind ( '}' ) . unwrap ( ) + 1 ;
293345
294- input = & input[ start + end + 2 ..input_end] ;
346+ // Find the matching closing brace
347+ let closing_brace_pos = start + 1 + end;
348+
349+ // Process the block
295350 styles. extend ( block) ;
351+
352+ // Update input to continue processing after the closing brace
353+ // Check if there's more content after the closing brace
354+ if closing_brace_pos + 1 < input. len ( ) {
355+ let remaining = & input[ closing_brace_pos + 1 ..] . trim ( ) ;
356+ if !remaining. is_empty ( ) {
357+ // If there's remaining text after the closing brace, process it
358+ // This handles cases like "} color: blue;"
359+ if remaining. contains ( '{' ) {
360+ // If it contains '{', continue the loop
361+ input = remaining;
362+ } else {
363+ // If it doesn't contain '{', process it as a block and break
364+ styles. extend ( css_to_style_block ( remaining, level, selector) ) ;
365+ break ;
366+ }
367+ } else {
368+ break ;
369+ }
370+ } else {
371+ break ;
372+ }
296373 }
297374 } else {
298375 styles. extend ( css_to_style_block ( input, level, selector) ) ;
@@ -669,6 +746,33 @@ mod tests {
669746 ( "background-color" , "red" , Some ( StyleSelector :: Selector ( "&:hover" . to_string( ) ) ) ) ,
670747 ]
671748 ) ]
749+ #[ case(
750+ "`background-color: red; &:hover { background-color: red; } color: blue;`" ,
751+ vec![
752+ ( "background-color" , "red" , None ) ,
753+ ( "background-color" , "red" , Some ( StyleSelector :: Selector ( "&:hover" . to_string( ) ) ) ) ,
754+ ( "color" , "blue" , None ) ,
755+ ]
756+ ) ]
757+ #[ case(
758+ "`background-color: red; &:hover { background-color: red; } color: blue; &:active { background-color: blue; }`" ,
759+ vec![
760+ ( "background-color" , "red" , None ) ,
761+ ( "background-color" , "red" , Some ( StyleSelector :: Selector ( "&:hover" . to_string( ) ) ) ) ,
762+ ( "color" , "blue" , None ) ,
763+ ( "background-color" , "blue" , Some ( StyleSelector :: Selector ( "&:active" . to_string( ) ) ) ) ,
764+ ]
765+ ) ]
766+ #[ case(
767+ "`background-color: red; &:hover { background-color: red; } color: blue; &:active { background-color: blue; } transform: rotate(90deg);`" ,
768+ vec![
769+ ( "background-color" , "red" , None ) ,
770+ ( "background-color" , "red" , Some ( StyleSelector :: Selector ( "&:hover" . to_string( ) ) ) ) ,
771+ ( "color" , "blue" , None ) ,
772+ ( "background-color" , "blue" , Some ( StyleSelector :: Selector ( "&:active" . to_string( ) ) ) ) ,
773+ ( "transform" , "rotate(90deg)" , None ) ,
774+ ]
775+ ) ]
672776 fn test_css_to_style_literal (
673777 #[ case] input : & str ,
674778 #[ case] expected : Vec < ( & str , & str , Option < StyleSelector > ) > ,
0 commit comments