Implement CSSX Phase 2: native SX style primitives

Replace Tailwind class strings with native SX expressions:
(css :flex :gap-4 :hover:bg-sky-200) instead of :class "flex gap-4 ..."

- Add style_dict.py: 516 atoms, variants, breakpoints, keyframes, patterns
- Add style_resolver.py: memoized resolver with variant splitting
- Add StyleValue type to types.py (frozen dataclass with class_name, declarations, etc.)
- Add css and merge-styles primitives to primitives.py
- Add defstyle and defkeyframes special forms to evaluator.py and async_eval.py
- Integrate StyleValue into html.py and async_eval.py render paths
- Add register_generated_rule() to css_registry.py, fix media query selector
- Add style dict JSON delivery with localStorage caching to helpers.py
- Add client-side css primitive, resolver, and style injection to sx.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 12:47:51 +00:00
parent 28388540d5
commit 19d59f5f4b
11 changed files with 1660 additions and 7 deletions

View File

@@ -27,7 +27,7 @@ from __future__ import annotations
import contextvars
from typing import Any
from .types import Component, Keyword, Lambda, Macro, NIL, Symbol
from .types import Component, Keyword, Lambda, Macro, NIL, StyleValue, Symbol
from .evaluator import _eval as _raw_eval, _call_component as _raw_call_component, _expand_macro, _trampoline
def _eval(expr, env):
@@ -479,6 +479,19 @@ def _render_element(tag: str, args: list, env: dict[str, Any]) -> str:
children.append(arg)
i += 1
# Handle :style StyleValue — convert to class and register CSS rule
style_val = attrs.get("style")
if isinstance(style_val, StyleValue):
from .css_registry import register_generated_rule
register_generated_rule(style_val)
# Merge into :class
existing_class = attrs.get("class")
if existing_class and existing_class is not NIL and existing_class is not False:
attrs["class"] = f"{existing_class} {style_val.class_name}"
else:
attrs["class"] = style_val.class_name
del attrs["style"]
# Collect CSS classes if collector is active
class_val = attrs.get("class")
if class_val is not None and class_val is not NIL and class_val is not False: