@@ -368,6 +368,63 @@ def ast_create_list_fragment(name: str, parent: str | None) -> ast.Assign:
368368 )
369369
370370
371+ def parse_text_interpolations (text : str ) -> list [tuple [bool , str ]]:
372+ """
373+ Parse text with {{expression}} interpolations.
374+
375+ Returns a list of tuples where:
376+ - (False, str) represents static text
377+ - (True, str) represents an expression to be evaluated
378+
379+ Examples:
380+ "Hello {{name}}" -> [(False, "Hello "), (True, "name")]
381+ "Static text" -> [(False, "Static text")]
382+ "{{a}} and {{b}}" -> [(True, "a"), (False, " and "), (True, "b")]
383+ """
384+ result = []
385+ i = 0
386+ n = len (text )
387+
388+ while i < n :
389+ # Look for start of interpolation
390+ start = text .find ("{{" , i )
391+ if start == - 1 :
392+ # No more interpolations, add remaining text
393+ if i < n :
394+ result .append ((False , text [i :]))
395+ break
396+
397+ # Check for escape sequence \{{
398+ if start > 0 and text [start - 1 ] == "\\ " :
399+ # Escaped - include text up to and including the {{ as static
400+ # but remove the backslash
401+ if i < start - 1 :
402+ result .append ((False , text [i : start - 1 ]))
403+ result .append ((False , "{{" ))
404+ i = start + 2
405+ continue
406+
407+ # Add static text before the interpolation
408+ if start > i :
409+ result .append ((False , text [i :start ]))
410+
411+ # Find the end of the interpolation
412+ end = text .find ("}}" , start + 2 )
413+ if end == - 1 :
414+ # Unclosed interpolation - treat rest as static
415+ result .append ((False , text [start :]))
416+ break
417+
418+ # Extract the expression
419+ expr = text [start + 2 : end ].strip ()
420+ if expr :
421+ result .append ((True , expr ))
422+
423+ i = end + 2
424+
425+ return result
426+
427+
371428def safe_tag (tag ):
372429 return tag .replace ("-" , "_" ).replace ("." , "_" )
373430
@@ -550,9 +607,72 @@ def create_children(
550607 # Ignore comments
551608 continue
552609 if isinstance (child , TextElement ):
553- # children_args.extend(args_for_text_element(child, names=names))
554- # continue
555- raise NotImplementedError ()
610+ # Create a text element fragment
611+ text_el = f"text{ counter ['text' ]} "
612+ counter ["text" ] += 1
613+
614+ # Create the TEXT_ELEMENT fragment
615+ result .append (
616+ ast .Assign (
617+ targets = [ast .Name (id = text_el , ctx = ast .Store ())],
618+ value = ast .Call (
619+ func = ast .Name (id = "Fragment" , ctx = ast .Load ()),
620+ args = [ast .Name (id = "renderer" , ctx = ast .Load ())],
621+ keywords = [
622+ ast .keyword (
623+ arg = "tag" , value = ast .Constant (value = "TEXT_ELEMENT" )
624+ ),
625+ ast .keyword (
626+ arg = "parent" ,
627+ value = ast .Name (id = target , ctx = ast .Load ())
628+ if target
629+ else ast .Constant (value = None ),
630+ ),
631+ ],
632+ ),
633+ )
634+ )
635+
636+ # Parse the text content for interpolations
637+ text_content = child .content
638+ has_interpolation = "{{" in text_content and "}}" in text_content
639+
640+ if has_interpolation :
641+ # Create a dynamic bind for text with interpolations
642+ # Build an f-string expression from the text
643+ parts = parse_text_interpolations (text_content )
644+ expr_parts = []
645+ for is_expr , part in parts :
646+ if is_expr :
647+ expr_parts .append ("{" + part + "}" )
648+ else :
649+ # Escape braces and backslashes in static parts for f-string
650+ # First escape backslashes that precede braces to avoid
651+ # invalid escape sequences like \{ in the f-string
652+ escaped = part .replace ("\\ {" , "\\ \\ {" ).replace (
653+ "\\ }" , "\\ \\ }"
654+ )
655+ # Then escape standalone braces for f-string syntax
656+ escaped = escaped .replace ("{" , "{{" ).replace ("}" , "}}" )
657+ expr_parts .append (escaped )
658+ fstring_content = "" .join (expr_parts )
659+ # Create the bind with an f-string
660+ source = ast .parse (
661+ f'{ text_el } .set_bind("content", lambda: f"{ fstring_content } ")' ,
662+ mode = "eval" ,
663+ )
664+ result .append (
665+ ast_named_lambda (
666+ source ,
667+ {"renderer" , "new" , text_el , "watch" } | names ,
668+ list_names ,
669+ )
670+ )
671+ else :
672+ # Static text - set attribute directly
673+ result .append (ast_set_attribute (text_el , "content" , text_content ))
674+
675+ continue
556676
557677 # Create element name
558678 tag = safe_tag (child .tag )
0 commit comments