Add SVG namespace auto-detection, custom elements, html: prefix, and fix filter/map tag collision
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
- Fix filter/map dispatching as HO functions when used as SVG/HTML tags (peek at first arg — Keyword means tag call, not function call) - Add html: prefix escape hatch to force any name to render as an element - Support custom elements (hyphenated names) per Web Components spec - SVG/MathML namespace auto-detection: client threads ns param through render chain; server uses _svg_context ContextVar so unknown tags inside (svg ...) or (math ...) render as elements without enumeration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,12 @@ css_class_collector: contextvars.ContextVar[set[str] | None] = contextvars.Conte
|
||||
"css_class_collector", default=None
|
||||
)
|
||||
|
||||
# ContextVar for SVG/MathML namespace auto-detection.
|
||||
# When True, unknown tag names inside (svg ...) or (math ...) are treated as elements.
|
||||
_svg_context: contextvars.ContextVar[bool] = contextvars.ContextVar(
|
||||
"_svg_context", default=False
|
||||
)
|
||||
|
||||
|
||||
class _RawHTML:
|
||||
"""Marker for pre-rendered HTML that should not be escaped."""
|
||||
@@ -430,10 +436,19 @@ def _render_list(expr: list, env: dict[str, Any]) -> str:
|
||||
if name == "<>":
|
||||
return "".join(_render(child, env) for child in expr[1:])
|
||||
|
||||
# --- html: prefix → force tag rendering --------------------------
|
||||
if name.startswith("html:"):
|
||||
return _render_element(name[5:], expr[1:], env)
|
||||
|
||||
# --- Render-aware special forms --------------------------------------
|
||||
# Check BEFORE HTML_TAGS because some names overlap (e.g. `map`).
|
||||
if name in _RENDER_FORMS:
|
||||
return _RENDER_FORMS[name](expr, env)
|
||||
# But if the name is ALSO an HTML tag and first arg is a Keyword,
|
||||
# it's a tag call (e.g. (filter :id "x" ...)), not a HO function.
|
||||
rsf = _RENDER_FORMS.get(name)
|
||||
if rsf is not None:
|
||||
if name in HTML_TAGS and len(expr) > 1 and isinstance(expr[1], Keyword):
|
||||
return _render_element(name, expr[1:], env)
|
||||
return rsf(expr, env)
|
||||
|
||||
# --- Macro expansion → expand then render --------------------------
|
||||
if name in env:
|
||||
@@ -453,6 +468,14 @@ def _render_list(expr: list, env: dict[str, Any]) -> str:
|
||||
return _render_component(val, expr[1:], env)
|
||||
# Fall through to evaluation
|
||||
|
||||
# --- Custom element (hyphenated name) → render as tag ------------
|
||||
if "-" in name:
|
||||
return _render_element(name, expr[1:], env)
|
||||
|
||||
# --- SVG/MathML context → unknown names are child elements --------
|
||||
if _svg_context.get(False):
|
||||
return _render_element(name, expr[1:], env)
|
||||
|
||||
# --- Other special forms / function calls → evaluate then render ---
|
||||
result = _eval(expr, env)
|
||||
return _render(result, env)
|
||||
@@ -524,7 +547,15 @@ def _render_element(tag: str, args: list, env: dict[str, Any]) -> str:
|
||||
if tag in VOID_ELEMENTS:
|
||||
return opening
|
||||
|
||||
# Render children
|
||||
child_html = "".join(_render(child, env) for child in children)
|
||||
# SVG/MathML namespace auto-detection: set context for children
|
||||
token = None
|
||||
if tag in ("svg", "math"):
|
||||
token = _svg_context.set(True)
|
||||
|
||||
try:
|
||||
child_html = "".join(_render(child, env) for child in children)
|
||||
finally:
|
||||
if token is not None:
|
||||
_svg_context.reset(token)
|
||||
|
||||
return f"{opening}{child_html}</{tag}>"
|
||||
|
||||
Reference in New Issue
Block a user