Merge branch 'worktree-cssx-components' into macros
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 14m0s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 14m0s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,15 +60,6 @@
|
|||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
|
|
||||||
function StyleValue(className, declarations, mediaRules, pseudoRules, keyframes) {
|
|
||||||
this.className = className;
|
|
||||||
this.declarations = declarations || "";
|
|
||||||
this.mediaRules = mediaRules || [];
|
|
||||||
this.pseudoRules = pseudoRules || [];
|
|
||||||
this.keyframes = keyframes || [];
|
|
||||||
}
|
|
||||||
StyleValue.prototype._styleValue = true;
|
|
||||||
|
|
||||||
function isSym(x) { return x != null && x._sym === true; }
|
function isSym(x) { return x != null && x._sym === true; }
|
||||||
function isKw(x) { return x != null && x._kw === true; }
|
function isKw(x) { return x != null && x._kw === true; }
|
||||||
|
|
||||||
@@ -104,7 +95,6 @@
|
|||||||
if (x._component) return "component";
|
if (x._component) return "component";
|
||||||
if (x._macro) return "macro";
|
if (x._macro) return "macro";
|
||||||
if (x._raw) return "raw-html";
|
if (x._raw) return "raw-html";
|
||||||
if (x._styleValue) return "style-value";
|
|
||||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||||
if (Array.isArray(x)) return "list";
|
if (Array.isArray(x)) return "list";
|
||||||
if (typeof x === "object") return "dict";
|
if (typeof x === "object") return "dict";
|
||||||
@@ -152,27 +142,6 @@
|
|||||||
function isComponent(x) { return x != null && x._component === true; }
|
function isComponent(x) { return x != null && x._component === true; }
|
||||||
function isMacro(x) { return x != null && x._macro === true; }
|
function isMacro(x) { return x != null && x._macro === true; }
|
||||||
|
|
||||||
function isStyleValue(x) { return x != null && x._styleValue === true; }
|
|
||||||
function styleValueClass(x) { return x.className; }
|
|
||||||
function styleValue_p(x) { return x != null && x._styleValue === true; }
|
|
||||||
|
|
||||||
function buildKeyframes(kfName, steps, env) {
|
|
||||||
// Platform implementation of defkeyframes
|
|
||||||
var parts = [];
|
|
||||||
for (var i = 0; i < steps.length; i++) {
|
|
||||||
var step = steps[i];
|
|
||||||
var selector = isSym(step[0]) ? step[0].name : String(step[0]);
|
|
||||||
var body = trampoline(evalExpr(step[1], env));
|
|
||||||
var decls = isStyleValue(body) ? body.declarations : String(body);
|
|
||||||
parts.push(selector + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
var kfRule = "@keyframes " + kfName + "{" + parts.join("") + "}";
|
|
||||||
var cn = "sx-ref-kf-" + kfName;
|
|
||||||
var sv = new StyleValue(cn, "animation-name:" + kfName, [], [], [[kfName, kfRule]]);
|
|
||||||
env[kfName] = sv;
|
|
||||||
return sv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function envHas(env, name) { return name in env; }
|
function envHas(env, name) { return name in env; }
|
||||||
function envGet(env, name) { return env[name]; }
|
function envGet(env, name) { return env[name]; }
|
||||||
function envSet(env, name, val) { env[name] = val; }
|
function envSet(env, name, val) { env[name] = val; }
|
||||||
@@ -386,29 +355,6 @@
|
|||||||
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
|
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
|
||||||
|
|
||||||
|
|
||||||
// stdlib.style
|
|
||||||
PRIMITIVES["css"] = function() {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return new StyleValue("sx-" + atoms.join("-"), atoms.join(";"), [], [], []);
|
|
||||||
};
|
|
||||||
PRIMITIVES["merge-styles"] = function() {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
var allDecls = valid.map(function(v) { return v.declarations; }).join(";");
|
|
||||||
return new StyleValue("sx-merged", allDecls, [], [], []);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// stdlib.debug
|
// stdlib.debug
|
||||||
PRIMITIVES["assert"] = function(cond, msg) {
|
PRIMITIVES["assert"] = function(cond, msg) {
|
||||||
if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed"));
|
if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed"));
|
||||||
@@ -495,7 +441,7 @@
|
|||||||
function isSpecialForm(n) { return n in {
|
function isSpecialForm(n) { return n in {
|
||||||
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
||||||
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
||||||
"defkeyframes":1,"defhandler":1,"begin":1,"do":1,
|
"defhandler":1,"begin":1,"do":1,
|
||||||
"quote":1,"quasiquote":1,"->":1,"set!":1
|
"quote":1,"quasiquote":1,"->":1,"set!":1
|
||||||
}; }
|
}; }
|
||||||
function isHoForm(n) { return n in {
|
function isHoForm(n) { return n in {
|
||||||
@@ -506,7 +452,7 @@
|
|||||||
|
|
||||||
function isDefinitionForm(name) {
|
function isDefinitionForm(name) {
|
||||||
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
||||||
name === "defstyle" || name === "defkeyframes" || name === "defhandler";
|
name === "defstyle" || name === "defhandler";
|
||||||
}
|
}
|
||||||
|
|
||||||
function indexOf_(s, ch) {
|
function indexOf_(s, ch) {
|
||||||
@@ -606,10 +552,10 @@
|
|||||||
var args = rest(expr);
|
var args = rest(expr);
|
||||||
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
||||||
var name = symbolName(head);
|
var name = symbolName(head);
|
||||||
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defkeyframes")) ? sfDefkeyframes(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
||||||
var mac = envGet(env, name);
|
var mac = envGet(env, name);
|
||||||
return makeThunk(expandMacro(mac, args, env), env);
|
return makeThunk(expandMacro(mac, args, env), env);
|
||||||
})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))))))))));
|
})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))));
|
||||||
})() : evalCall(head, args, env)));
|
})() : evalCall(head, args, env)));
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -844,13 +790,6 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
return value;
|
return value;
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// sf-defkeyframes
|
|
||||||
var sfDefkeyframes = function(args, env) { return (function() {
|
|
||||||
var kfName = symbolName(first(args));
|
|
||||||
var steps = rest(args);
|
|
||||||
return buildKeyframes(kfName, steps, env);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// sf-begin
|
// sf-begin
|
||||||
var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); };
|
var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); };
|
||||||
|
|
||||||
@@ -1010,7 +949,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"];
|
var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"];
|
||||||
|
|
||||||
// definition-form?
|
// definition-form?
|
||||||
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defkeyframes"), (name == "defhandler")); };
|
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler")); };
|
||||||
|
|
||||||
// parse-element-args
|
// parse-element-args
|
||||||
var parseElementArgs = function(args, env) { return (function() {
|
var parseElementArgs = function(args, env) { return (function() {
|
||||||
@@ -1030,7 +969,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
// render-attrs
|
// render-attrs
|
||||||
var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() {
|
var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() {
|
||||||
var val = dictGet(attrs, key);
|
var val = dictGet(attrs, key);
|
||||||
return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !isSxTruthy(val))) ? "" : (isSxTruthy(isNil(val)) ? "" : (isSxTruthy((isSxTruthy((key == "style")) && isStyleValue(val))) ? (String(" class=\"") + String(styleValueClass(val)) + String("\"")) : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\""))))));
|
return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !isSxTruthy(val))) ? "" : (isSxTruthy(isNil(val)) ? "" : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\"")))));
|
||||||
})(); }, keys(attrs))); };
|
})(); }, keys(attrs))); };
|
||||||
|
|
||||||
// eval-cond
|
// eval-cond
|
||||||
@@ -1181,10 +1120,10 @@ continue; } else { return NIL; } } };
|
|||||||
var renderToHtml = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); };
|
var renderToHtml = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); };
|
||||||
|
|
||||||
// render-value-to-html
|
// render-value-to-html
|
||||||
var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "style-value") return styleValueClass(val); return escapeHtml((String(val))); })(); };
|
var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); return escapeHtml((String(val))); })(); };
|
||||||
|
|
||||||
// RENDER_HTML_FORMS
|
// RENDER_HTML_FORMS
|
||||||
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defkeyframes", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
||||||
|
|
||||||
// render-html-form?
|
// render-html-form?
|
||||||
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
|
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
|
||||||
@@ -1332,7 +1271,7 @@ continue; } else { return NIL; } } };
|
|||||||
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
|
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
|
||||||
|
|
||||||
// render-to-dom
|
// render-to-dom
|
||||||
var renderToDom = function(expr, env, ns) { return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); if (_m == "style-value") return createTextNode(styleValueClass(expr)); return createTextNode((String(expr))); })(); };
|
var renderToDom = function(expr, env, ns) { return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return createTextNode((String(expr))); })(); };
|
||||||
|
|
||||||
// render-dom-list
|
// render-dom-list
|
||||||
var renderDomList = function(expr, env, ns) { return (function() {
|
var renderDomList = function(expr, env, ns) { return (function() {
|
||||||
@@ -1355,22 +1294,15 @@ continue; } else { return NIL; } } };
|
|||||||
var renderDomElement = function(tag, args, env, ns) { return (function() {
|
var renderDomElement = function(tag, args, env, ns) { return (function() {
|
||||||
var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns));
|
var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns));
|
||||||
var el = domCreateElement(tag, newNs);
|
var el = domCreateElement(tag, newNs);
|
||||||
var extraClass = NIL;
|
|
||||||
reduce(function(state, arg) { return (function() {
|
reduce(function(state, arg) { return (function() {
|
||||||
var skip = get(state, "skip");
|
var skip = get(state, "skip");
|
||||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
||||||
var attrName = keywordName(arg);
|
var attrName = keywordName(arg);
|
||||||
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy((attrName == "style")) && isStyleValue(attrVal))) ? (extraClass = styleValueClass(attrVal)) : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))));
|
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal))))));
|
||||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||||
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
||||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
if (isSxTruthy(extraClass)) {
|
|
||||||
(function() {
|
|
||||||
var existing = domGetAttr(el, "class");
|
|
||||||
return domSetAttr(el, "class", (isSxTruthy(existing) ? (String(existing) + String(" ") + String(extraClass)) : extraClass));
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
return el;
|
return el;
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -1421,7 +1353,7 @@ continue; } else { return NIL; } } };
|
|||||||
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
|
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
|
||||||
|
|
||||||
// RENDER_DOM_FORMS
|
// RENDER_DOM_FORMS
|
||||||
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defkeyframes", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
||||||
|
|
||||||
// render-dom-form?
|
// render-dom-form?
|
||||||
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
||||||
@@ -2196,180 +2128,6 @@ return bindInlineHandlers(root); };
|
|||||||
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
|
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from cssx ===
|
|
||||||
|
|
||||||
// _style-atoms
|
|
||||||
var _styleAtoms = {};
|
|
||||||
|
|
||||||
// _pseudo-variants
|
|
||||||
var _pseudoVariants = {};
|
|
||||||
|
|
||||||
// _responsive-breakpoints
|
|
||||||
var _responsiveBreakpoints = {};
|
|
||||||
|
|
||||||
// _style-keyframes
|
|
||||||
var _styleKeyframes = {};
|
|
||||||
|
|
||||||
// _arbitrary-patterns
|
|
||||||
var _arbitraryPatterns = [];
|
|
||||||
|
|
||||||
// _child-selector-prefixes
|
|
||||||
var _childSelectorPrefixes = [];
|
|
||||||
|
|
||||||
// _style-cache
|
|
||||||
var _styleCache = {};
|
|
||||||
|
|
||||||
// _injected-styles
|
|
||||||
var _injectedStyles = {};
|
|
||||||
|
|
||||||
// load-style-dict
|
|
||||||
var loadStyleDict = function(data) { _styleAtoms = sxOr(get(data, "a"), {});
|
|
||||||
_pseudoVariants = sxOr(get(data, "v"), {});
|
|
||||||
_responsiveBreakpoints = sxOr(get(data, "b"), {});
|
|
||||||
_styleKeyframes = sxOr(get(data, "k"), {});
|
|
||||||
_childSelectorPrefixes = sxOr(get(data, "c"), []);
|
|
||||||
_arbitraryPatterns = map(function(pair) { return {["re"]: compileRegex((String("^") + String(first(pair)) + String("$"))), ["tmpl"]: nth(pair, 1)}; }, sxOr(get(data, "p"), []));
|
|
||||||
return (_styleCache = {}); };
|
|
||||||
|
|
||||||
// split-variant
|
|
||||||
var splitVariant = function(atom) { return (function() {
|
|
||||||
var result = NIL;
|
|
||||||
{ var _c = keys(_responsiveBreakpoints); for (var _i = 0; _i < _c.length; _i++) { var bp = _c[_i]; if (isSxTruthy(isNil(result))) {
|
|
||||||
(function() {
|
|
||||||
var prefix = (String(bp) + String(":"));
|
|
||||||
return (isSxTruthy(startsWith(atom, prefix)) ? (function() {
|
|
||||||
var restAtom = slice(atom, len(prefix));
|
|
||||||
return (function() {
|
|
||||||
var innerMatch = NIL;
|
|
||||||
{ var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(innerMatch))) {
|
|
||||||
(function() {
|
|
||||||
var innerPrefix = (String(pv) + String(":"));
|
|
||||||
return (isSxTruthy(startsWith(restAtom, innerPrefix)) ? (innerMatch = [(String(bp) + String(":") + String(pv)), slice(restAtom, len(innerPrefix))]) : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return (result = sxOr(innerMatch, [bp, restAtom]));
|
|
||||||
})();
|
|
||||||
})() : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
if (isSxTruthy(isNil(result))) {
|
|
||||||
{ var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(result))) {
|
|
||||||
(function() {
|
|
||||||
var prefix = (String(pv) + String(":"));
|
|
||||||
return (isSxTruthy(startsWith(atom, prefix)) ? (result = [pv, slice(atom, len(prefix))]) : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
}
|
|
||||||
return sxOr(result, [NIL, atom]);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// resolve-atom
|
|
||||||
var resolveAtom = function(atom) { return (function() {
|
|
||||||
var decls = dictGet(_styleAtoms, atom);
|
|
||||||
return (isSxTruthy(!isSxTruthy(isNil(decls))) ? decls : (isSxTruthy(startsWith(atom, "animate-")) ? (function() {
|
|
||||||
var kfName = slice(atom, 8);
|
|
||||||
return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? (String("animation-name:") + String(kfName)) : NIL);
|
|
||||||
})() : (function() {
|
|
||||||
var matchResult = NIL;
|
|
||||||
{ var _c = _arbitraryPatterns; for (var _i = 0; _i < _c.length; _i++) { var pat = _c[_i]; if (isSxTruthy(isNil(matchResult))) {
|
|
||||||
(function() {
|
|
||||||
var m = regexMatch(get(pat, "re"), atom);
|
|
||||||
return (isSxTruthy(m) ? (matchResult = regexReplaceGroups(get(pat, "tmpl"), m)) : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return matchResult;
|
|
||||||
})()));
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// is-child-selector-atom?
|
|
||||||
var isChildSelectorAtom = function(atom) { return some(function(prefix) { return startsWith(atom, prefix); }, _childSelectorPrefixes); };
|
|
||||||
|
|
||||||
// hash-style
|
|
||||||
var hashStyle = function(input) { return fnv1aHash(input); };
|
|
||||||
|
|
||||||
// resolve-style
|
|
||||||
var resolveStyle = function(atoms) { return (function() {
|
|
||||||
var key = join("\0", atoms);
|
|
||||||
return (function() {
|
|
||||||
var cached = dictGet(_styleCache, key);
|
|
||||||
return (isSxTruthy(!isSxTruthy(isNil(cached))) ? cached : (function() {
|
|
||||||
var baseDecls = [];
|
|
||||||
var mediaRules = [];
|
|
||||||
var pseudoRules = [];
|
|
||||||
var kfNeeded = [];
|
|
||||||
{ var _c = atoms; for (var _i = 0; _i < _c.length; _i++) { var a = _c[_i]; if (isSxTruthy(a)) {
|
|
||||||
(function() {
|
|
||||||
var clean = (isSxTruthy(startsWith(a, ":")) ? slice(a, 1) : a);
|
|
||||||
return (function() {
|
|
||||||
var parts = splitVariant(clean);
|
|
||||||
return (function() {
|
|
||||||
var variant = first(parts);
|
|
||||||
var base = nth(parts, 1);
|
|
||||||
var decls = resolveAtom(base);
|
|
||||||
return (isSxTruthy(decls) ? ((isSxTruthy(startsWith(base, "animate-")) ? (function() {
|
|
||||||
var kfName = slice(base, 8);
|
|
||||||
return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? append_b(kfNeeded, [kfName, dictGet(_styleKeyframes, kfName)]) : NIL);
|
|
||||||
})() : NIL), (isSxTruthy(isNil(variant)) ? (isSxTruthy(isChildSelectorAtom(base)) ? append_b(pseudoRules, [">:not(:first-child)", decls]) : append_b(baseDecls, decls)) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() {
|
|
||||||
var vparts = split(variant, ":");
|
|
||||||
var mediaPart = NIL;
|
|
||||||
var pseudoPart = NIL;
|
|
||||||
{ var _c = vparts; for (var _i = 0; _i < _c.length; _i++) { var vp = _c[_i]; (isSxTruthy(dictHas(_responsiveBreakpoints, vp)) ? (mediaPart = dictGet(_responsiveBreakpoints, vp)) : (isSxTruthy(dictHas(_pseudoVariants, vp)) ? (pseudoPart = dictGet(_pseudoVariants, vp)) : NIL)); } }
|
|
||||||
if (isSxTruthy(mediaPart)) {
|
|
||||||
mediaRules.push([mediaPart, decls]);
|
|
||||||
}
|
|
||||||
if (isSxTruthy(pseudoPart)) {
|
|
||||||
pseudoRules.push([pseudoPart, decls]);
|
|
||||||
}
|
|
||||||
return (isSxTruthy((isSxTruthy(isNil(mediaPart)) && isNil(pseudoPart))) ? append_b(baseDecls, decls) : NIL);
|
|
||||||
})())))) : NIL);
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return (function() {
|
|
||||||
var hashInput = join(";", baseDecls);
|
|
||||||
{ var _c = mediaRules; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } }
|
|
||||||
{ var _c = pseudoRules; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } }
|
|
||||||
{ var _c = kfNeeded; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } }
|
|
||||||
return (function() {
|
|
||||||
var cn = (String("sx-") + String(hashStyle(hashInput)));
|
|
||||||
var sv = makeStyleValue_(cn, join(";", baseDecls), mediaRules, pseudoRules, kfNeeded);
|
|
||||||
_styleCache[key] = sv;
|
|
||||||
injectStyleValue(sv, atoms);
|
|
||||||
return sv;
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
})());
|
|
||||||
})();
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// merge-style-values
|
|
||||||
var mergeStyleValues = function(styles) { return (isSxTruthy((len(styles) == 1)) ? first(styles) : (function() {
|
|
||||||
var allDecls = [];
|
|
||||||
var allMedia = [];
|
|
||||||
var allPseudo = [];
|
|
||||||
var allKf = [];
|
|
||||||
{ var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var sv = _c[_i]; if (isSxTruthy(styleValueDeclarations(sv))) {
|
|
||||||
allDecls.push(styleValueDeclarations(sv));
|
|
||||||
}
|
|
||||||
allMedia = concat(allMedia, styleValueMediaRules(sv));
|
|
||||||
allPseudo = concat(allPseudo, styleValuePseudoRules(sv));
|
|
||||||
allKf = concat(allKf, styleValueKeyframes_(sv)); } }
|
|
||||||
return (function() {
|
|
||||||
var hashInput = join(";", allDecls);
|
|
||||||
{ var _c = allMedia; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } }
|
|
||||||
{ var _c = allPseudo; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } }
|
|
||||||
{ var _c = allKf; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } }
|
|
||||||
return (function() {
|
|
||||||
var cn = (String("sx-") + String(hashStyle(hashInput)));
|
|
||||||
var merged = makeStyleValue_(cn, join(";", allDecls), allMedia, allPseudo, allKf);
|
|
||||||
injectStyleValue(merged, []);
|
|
||||||
return merged;
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
})()); };
|
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from boot ===
|
// === Transpiled from boot ===
|
||||||
|
|
||||||
// HEAD_HOIST_SELECTOR
|
// HEAD_HOIST_SELECTOR
|
||||||
@@ -2485,26 +2243,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
})();
|
})();
|
||||||
return setSxCompCookie(hash);
|
return setSxCompCookie(hash);
|
||||||
})());
|
})());
|
||||||
})(); };
|
|
||||||
|
|
||||||
// init-style-dict
|
|
||||||
var initStyleDict = function() { return (function() {
|
|
||||||
var scripts = queryStyleScripts();
|
|
||||||
return forEach(function(s) { return (isSxTruthy(!isSxTruthy(isProcessed(s, "styles"))) ? (markProcessed(s, "styles"), (function() {
|
|
||||||
var text = domTextContent(s);
|
|
||||||
var hash = domGetAttr(s, "data-hash");
|
|
||||||
return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? parseAndLoadStyleDict(text) : NIL) : (function() {
|
|
||||||
var hasInline = (isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))));
|
|
||||||
(function() {
|
|
||||||
var cachedHash = localStorageGet("sx-styles-hash");
|
|
||||||
return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo("styles: downloaded (cookie stale)")) : (function() {
|
|
||||||
var cached = localStorageGet("sx-styles-src");
|
|
||||||
return (isSxTruthy(cached) ? (parseAndLoadStyleDict(cached), logInfo((String("styles: cached (") + String(hash) + String(")")))) : (clearSxStylesCookie(), browserReload()));
|
|
||||||
})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-hash", hash), localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo((String("styles: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-styles-hash"), localStorageRemove("sx-styles-src"), clearSxStylesCookie(), browserReload())));
|
|
||||||
})();
|
|
||||||
return setSxStylesCookie(hash);
|
|
||||||
})());
|
|
||||||
})()) : NIL); }, scripts);
|
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// _page-routes
|
// _page-routes
|
||||||
@@ -2530,7 +2268,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// boot-init
|
// boot-init
|
||||||
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), initStyleDict(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); };
|
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from router (client-side route matching) ===
|
// === Transpiled from router (client-side route matching) ===
|
||||||
@@ -2803,7 +2541,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
renderDomElement = function(tag, args, env, ns) {
|
renderDomElement = function(tag, args, env, ns) {
|
||||||
var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns;
|
var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns;
|
||||||
var el = domCreateElement(tag, newNs);
|
var el = domCreateElement(tag, newNs);
|
||||||
var extraClasses = [];
|
|
||||||
var isVoid = contains(VOID_ELEMENTS, tag);
|
var isVoid = contains(VOID_ELEMENTS, tag);
|
||||||
for (var i = 0; i < args.length; i++) {
|
for (var i = 0; i < args.length; i++) {
|
||||||
var arg = args[i];
|
var arg = args[i];
|
||||||
@@ -2812,11 +2549,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
var attrVal = trampoline(evalExpr(args[i + 1], env));
|
var attrVal = trampoline(evalExpr(args[i + 1], env));
|
||||||
i++; // skip value
|
i++; // skip value
|
||||||
if (isNil(attrVal) || attrVal === false) continue;
|
if (isNil(attrVal) || attrVal === false) continue;
|
||||||
if (attrName === "class" && attrVal && attrVal._styleValue) {
|
if (contains(BOOLEAN_ATTRS, attrName)) {
|
||||||
extraClasses.push(attrVal.className);
|
|
||||||
} else if (attrName === "style" && attrVal && attrVal._styleValue) {
|
|
||||||
extraClasses.push(attrVal.className);
|
|
||||||
} else if (contains(BOOLEAN_ATTRS, attrName)) {
|
|
||||||
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
||||||
} else if (attrVal === true) {
|
} else if (attrVal === true) {
|
||||||
el.setAttribute(attrName, "");
|
el.setAttribute(attrName, "");
|
||||||
@@ -2830,10 +2563,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (extraClasses.length) {
|
|
||||||
var existing = el.getAttribute("class") || "";
|
|
||||||
el.setAttribute("class", (existing ? existing + " " : "") + extraClasses.join(" "));
|
|
||||||
}
|
|
||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3727,70 +3456,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeStyleValue_(cn, decls, media, pseudo, kf) {
|
|
||||||
return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function styleValueDeclarations(sv) { return sv.declarations; }
|
|
||||||
function styleValueMediaRules(sv) { return sv.mediaRules; }
|
|
||||||
function styleValuePseudoRules(sv) { return sv.pseudoRules; }
|
|
||||||
function styleValueKeyframes_(sv) { return sv.keyframes; }
|
|
||||||
|
|
||||||
function injectStyleValue(sv, atoms) {
|
|
||||||
if (_injectedStyles[sv.className]) return;
|
|
||||||
_injectedStyles[sv.className] = true;
|
|
||||||
|
|
||||||
if (!_hasDom) return;
|
|
||||||
var cssTarget = document.getElementById("sx-css");
|
|
||||||
if (!cssTarget) return;
|
|
||||||
|
|
||||||
var rules = [];
|
|
||||||
// Child-selector atoms are now routed to pseudoRules by the resolver
|
|
||||||
// with selector ">:not(:first-child)", so base declarations are always
|
|
||||||
// applied directly to the class.
|
|
||||||
if (sv.declarations) {
|
|
||||||
rules.push("." + sv.className + "{" + sv.declarations + "}");
|
|
||||||
}
|
|
||||||
for (var pi = 0; pi < sv.pseudoRules.length; pi++) {
|
|
||||||
var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1];
|
|
||||||
if (sel.indexOf("&") >= 0) {
|
|
||||||
rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}");
|
|
||||||
} else {
|
|
||||||
rules.push("." + sv.className + sel + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var mi = 0; mi < sv.mediaRules.length; mi++) {
|
|
||||||
rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}");
|
|
||||||
}
|
|
||||||
for (var ki = 0; ki < sv.keyframes.length; ki++) {
|
|
||||||
rules.push(sv.keyframes[ki][1]);
|
|
||||||
}
|
|
||||||
cssTarget.textContent += rules.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace stub css primitive with real CSSX implementation
|
|
||||||
PRIMITIVES["css"] = function() {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return resolveStyle(atoms);
|
|
||||||
};
|
|
||||||
|
|
||||||
PRIMITIVES["merge-styles"] = function() {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
return mergeStyleValues(valid);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -3847,12 +3512,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
r.querySelectorAll('script[type="text/sx"]'));
|
r.querySelectorAll('script[type="text/sx"]'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryStyleScripts() {
|
|
||||||
if (!_hasDom) return [];
|
|
||||||
return Array.prototype.slice.call(
|
|
||||||
document.querySelectorAll('script[type="text/sx-styles"]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryPageScripts() {
|
function queryPageScripts() {
|
||||||
if (!_hasDom) return [];
|
if (!_hasDom) return [];
|
||||||
return Array.prototype.slice.call(
|
return Array.prototype.slice.call(
|
||||||
@@ -3884,14 +3543,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSxStylesCookie(hash) {
|
|
||||||
if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSxStylesCookie() {
|
|
||||||
if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Env helpers ---
|
// --- Env helpers ---
|
||||||
|
|
||||||
function parseEnvAttr(el) {
|
function parseEnvAttr(el) {
|
||||||
@@ -3940,12 +3591,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAndLoadStyleDict(text) {
|
|
||||||
try { loadStyleDict(JSON.parse(text)); }
|
|
||||||
catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Post-transpilation fixups
|
// Post-transpilation fixups
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -4106,7 +3751,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
|
|
||||||
// define/defcomp/defmacro — eval for side effects
|
// define/defcomp/defmacro — eval for side effects
|
||||||
if (hname === "define" || hname === "defcomp" || hname === "defmacro" ||
|
if (hname === "define" || hname === "defcomp" || hname === "defmacro" ||
|
||||||
hname === "defstyle" || hname === "defkeyframes" || hname === "defhandler") {
|
hname === "defstyle" || hname === "defhandler") {
|
||||||
trampoline(evalExpr(expr, env));
|
trampoline(evalExpr(expr, env));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -4228,11 +3873,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
})(attrName, attrVal);
|
})(attrName, attrVal);
|
||||||
} else {
|
} else {
|
||||||
if (!isNil(attrVal) && attrVal !== false) {
|
if (!isNil(attrVal) && attrVal !== false) {
|
||||||
if (attrName === "class" && attrVal && attrVal._styleValue) {
|
if (contains(BOOLEAN_ATTRS, attrName)) {
|
||||||
el.setAttribute("class", (el.getAttribute("class") || "") + " " + attrVal.className);
|
|
||||||
} else if (attrName === "style" && attrVal && attrVal._styleValue) {
|
|
||||||
el.setAttribute("class", (el.getAttribute("class") || "") + " " + attrVal.className);
|
|
||||||
} else if (contains(BOOLEAN_ATTRS, attrName)) {
|
|
||||||
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
||||||
} else if (attrVal === true) {
|
} else if (attrVal === true) {
|
||||||
el.setAttribute(attrName, "");
|
el.setAttribute(attrName, "");
|
||||||
@@ -4694,7 +4335,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null,
|
registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null,
|
||||||
asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,
|
asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,
|
||||||
asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,
|
asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,
|
||||||
_version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,15 +58,6 @@
|
|||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
|
|
||||||
function StyleValue(className, declarations, mediaRules, pseudoRules, keyframes) {
|
|
||||||
this.className = className;
|
|
||||||
this.declarations = declarations || "";
|
|
||||||
this.mediaRules = mediaRules || [];
|
|
||||||
this.pseudoRules = pseudoRules || [];
|
|
||||||
this.keyframes = keyframes || [];
|
|
||||||
}
|
|
||||||
StyleValue.prototype._styleValue = true;
|
|
||||||
|
|
||||||
function isSym(x) { return x != null && x._sym === true; }
|
function isSym(x) { return x != null && x._sym === true; }
|
||||||
function isKw(x) { return x != null && x._kw === true; }
|
function isKw(x) { return x != null && x._kw === true; }
|
||||||
|
|
||||||
@@ -102,7 +93,6 @@
|
|||||||
if (x._component) return "component";
|
if (x._component) return "component";
|
||||||
if (x._macro) return "macro";
|
if (x._macro) return "macro";
|
||||||
if (x._raw) return "raw-html";
|
if (x._raw) return "raw-html";
|
||||||
if (x._styleValue) return "style-value";
|
|
||||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||||
if (Array.isArray(x)) return "list";
|
if (Array.isArray(x)) return "list";
|
||||||
if (typeof x === "object") return "dict";
|
if (typeof x === "object") return "dict";
|
||||||
@@ -149,27 +139,6 @@
|
|||||||
function isComponent(x) { return x != null && x._component === true; }
|
function isComponent(x) { return x != null && x._component === true; }
|
||||||
function isMacro(x) { return x != null && x._macro === true; }
|
function isMacro(x) { return x != null && x._macro === true; }
|
||||||
|
|
||||||
function isStyleValue(x) { return x != null && x._styleValue === true; }
|
|
||||||
function styleValueClass(x) { return x.className; }
|
|
||||||
function styleValue_p(x) { return x != null && x._styleValue === true; }
|
|
||||||
|
|
||||||
function buildKeyframes(kfName, steps, env) {
|
|
||||||
// Platform implementation of defkeyframes
|
|
||||||
var parts = [];
|
|
||||||
for (var i = 0; i < steps.length; i++) {
|
|
||||||
var step = steps[i];
|
|
||||||
var selector = isSym(step[0]) ? step[0].name : String(step[0]);
|
|
||||||
var body = trampoline(evalExpr(step[1], env));
|
|
||||||
var decls = isStyleValue(body) ? body.declarations : String(body);
|
|
||||||
parts.push(selector + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
var kfRule = "@keyframes " + kfName + "{" + parts.join("") + "}";
|
|
||||||
var cn = "sx-ref-kf-" + kfName;
|
|
||||||
var sv = new StyleValue(cn, "animation-name:" + kfName, [], [], [[kfName, kfRule]]);
|
|
||||||
env[kfName] = sv;
|
|
||||||
return sv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function envHas(env, name) { return name in env; }
|
function envHas(env, name) { return name in env; }
|
||||||
function envGet(env, name) { return env[name]; }
|
function envGet(env, name) { return env[name]; }
|
||||||
function envSet(env, name, val) { env[name] = val; }
|
function envSet(env, name, val) { env[name] = val; }
|
||||||
@@ -377,29 +346,6 @@
|
|||||||
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
|
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
|
||||||
|
|
||||||
|
|
||||||
// stdlib.style
|
|
||||||
PRIMITIVES["css"] = function() {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return new StyleValue("sx-" + atoms.join("-"), atoms.join(";"), [], [], []);
|
|
||||||
};
|
|
||||||
PRIMITIVES["merge-styles"] = function() {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
var allDecls = valid.map(function(v) { return v.declarations; }).join(";");
|
|
||||||
return new StyleValue("sx-merged", allDecls, [], [], []);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// stdlib.debug
|
// stdlib.debug
|
||||||
PRIMITIVES["assert"] = function(cond, msg) {
|
PRIMITIVES["assert"] = function(cond, msg) {
|
||||||
if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed"));
|
if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed"));
|
||||||
@@ -486,7 +432,7 @@
|
|||||||
function isSpecialForm(n) { return n in {
|
function isSpecialForm(n) { return n in {
|
||||||
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
||||||
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
||||||
"defkeyframes":1,"defhandler":1,"begin":1,"do":1,
|
"defhandler":1,"begin":1,"do":1,
|
||||||
"quote":1,"quasiquote":1,"->":1,"set!":1
|
"quote":1,"quasiquote":1,"->":1,"set!":1
|
||||||
}; }
|
}; }
|
||||||
function isHoForm(n) { return n in {
|
function isHoForm(n) { return n in {
|
||||||
@@ -517,7 +463,7 @@
|
|||||||
|
|
||||||
function isDefinitionForm(name) {
|
function isDefinitionForm(name) {
|
||||||
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
||||||
name === "defstyle" || name === "defkeyframes" || name === "defhandler";
|
name === "defstyle" || name === "defhandler";
|
||||||
}
|
}
|
||||||
|
|
||||||
function indexOf_(s, ch) {
|
function indexOf_(s, ch) {
|
||||||
@@ -657,10 +603,10 @@
|
|||||||
var args = rest(expr);
|
var args = rest(expr);
|
||||||
return (isSxTruthy(!sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list"))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
return (isSxTruthy(!sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list"))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
||||||
var name = symbolName(head);
|
var name = symbolName(head);
|
||||||
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defkeyframes")) ? sfDefkeyframes(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
||||||
var mac = envGet(env, name);
|
var mac = envGet(env, name);
|
||||||
return makeThunk(expandMacro(mac, args, env), env);
|
return makeThunk(expandMacro(mac, args, env), env);
|
||||||
})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))))))))));
|
})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))));
|
||||||
})() : evalCall(head, args, env)));
|
})() : evalCall(head, args, env)));
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -881,13 +827,6 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
return value;
|
return value;
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// sf-defkeyframes
|
|
||||||
var sfDefkeyframes = function(args, env) { return (function() {
|
|
||||||
var kfName = symbolName(first(args));
|
|
||||||
var steps = rest(args);
|
|
||||||
return buildKeyframes(kfName, steps, env);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// sf-begin
|
// sf-begin
|
||||||
var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); };
|
var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); };
|
||||||
|
|
||||||
@@ -1047,7 +986,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"];
|
var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"];
|
||||||
|
|
||||||
// definition-form?
|
// definition-form?
|
||||||
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defkeyframes"), (name == "defhandler")); };
|
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler")); };
|
||||||
|
|
||||||
// parse-element-args
|
// parse-element-args
|
||||||
var parseElementArgs = function(args, env) { return (function() {
|
var parseElementArgs = function(args, env) { return (function() {
|
||||||
@@ -1067,7 +1006,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
// render-attrs
|
// render-attrs
|
||||||
var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() {
|
var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() {
|
||||||
var val = dictGet(attrs, key);
|
var val = dictGet(attrs, key);
|
||||||
return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !val)) ? "" : (isSxTruthy(isNil(val)) ? "" : (isSxTruthy((isSxTruthy((key == "style")) && isStyleValue(val))) ? (String(" class=\"") + String(styleValueClass(val)) + String("\"")) : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\""))))));
|
return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !val)) ? "" : (isSxTruthy(isNil(val)) ? "" : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\"")))));
|
||||||
})(); }, keys(attrs))); };
|
})(); }, keys(attrs))); };
|
||||||
|
|
||||||
|
|
||||||
@@ -1188,10 +1127,10 @@ continue; } else { return NIL; } } };
|
|||||||
var renderToHtml = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); };
|
var renderToHtml = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); };
|
||||||
|
|
||||||
// render-value-to-html
|
// render-value-to-html
|
||||||
var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "style-value") return styleValueClass(val); return escapeHtml((String(val))); })(); };
|
var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); return escapeHtml((String(val))); })(); };
|
||||||
|
|
||||||
// RENDER_HTML_FORMS
|
// RENDER_HTML_FORMS
|
||||||
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defkeyframes", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
||||||
|
|
||||||
// render-html-form?
|
// render-html-form?
|
||||||
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
|
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
|
||||||
@@ -1339,7 +1278,7 @@ continue; } else { return NIL; } } };
|
|||||||
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
|
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
|
||||||
|
|
||||||
// render-to-dom
|
// render-to-dom
|
||||||
var renderToDom = function(expr, env, ns) { return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); if (_m == "style-value") return createTextNode(styleValueClass(expr)); return createTextNode((String(expr))); })(); };
|
var renderToDom = function(expr, env, ns) { return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return createTextNode((String(expr))); })(); };
|
||||||
|
|
||||||
// render-dom-list
|
// render-dom-list
|
||||||
var renderDomList = function(expr, env, ns) { return (function() {
|
var renderDomList = function(expr, env, ns) { return (function() {
|
||||||
@@ -1362,22 +1301,15 @@ continue; } else { return NIL; } } };
|
|||||||
var renderDomElement = function(tag, args, env, ns) { return (function() {
|
var renderDomElement = function(tag, args, env, ns) { return (function() {
|
||||||
var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns));
|
var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns));
|
||||||
var el = domCreateElement(tag, newNs);
|
var el = domCreateElement(tag, newNs);
|
||||||
var extraClass = NIL;
|
|
||||||
reduce(function(state, arg) { return (function() {
|
reduce(function(state, arg) { return (function() {
|
||||||
var skip = get(state, "skip");
|
var skip = get(state, "skip");
|
||||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
||||||
var attrName = keywordName(arg);
|
var attrName = keywordName(arg);
|
||||||
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy((attrName == "style")) && isStyleValue(attrVal))) ? (extraClass = styleValueClass(attrVal)) : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))));
|
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal))))));
|
||||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||||
})() : ((isSxTruthy(!contains(VOID_ELEMENTS, tag)) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
})() : ((isSxTruthy(!contains(VOID_ELEMENTS, tag)) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
||||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
if (isSxTruthy(extraClass)) {
|
|
||||||
(function() {
|
|
||||||
var existing = domGetAttr(el, "class");
|
|
||||||
return domSetAttr(el, "class", (isSxTruthy(existing) ? (String(existing) + String(" ") + String(extraClass)) : extraClass));
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
return el;
|
return el;
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -1433,7 +1365,7 @@ continue; } else { return NIL; } } };
|
|||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// RENDER_DOM_FORMS
|
// RENDER_DOM_FORMS
|
||||||
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defkeyframes", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"];
|
||||||
|
|
||||||
// render-dom-form?
|
// render-dom-form?
|
||||||
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
||||||
@@ -2089,180 +2021,6 @@ return bindInlineHandlers(root); };
|
|||||||
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
|
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from cssx ===
|
|
||||||
|
|
||||||
// _style-atoms
|
|
||||||
var _styleAtoms = {};
|
|
||||||
|
|
||||||
// _pseudo-variants
|
|
||||||
var _pseudoVariants = {};
|
|
||||||
|
|
||||||
// _responsive-breakpoints
|
|
||||||
var _responsiveBreakpoints = {};
|
|
||||||
|
|
||||||
// _style-keyframes
|
|
||||||
var _styleKeyframes = {};
|
|
||||||
|
|
||||||
// _arbitrary-patterns
|
|
||||||
var _arbitraryPatterns = [];
|
|
||||||
|
|
||||||
// _child-selector-prefixes
|
|
||||||
var _childSelectorPrefixes = [];
|
|
||||||
|
|
||||||
// _style-cache
|
|
||||||
var _styleCache = {};
|
|
||||||
|
|
||||||
// _injected-styles
|
|
||||||
var _injectedStyles = {};
|
|
||||||
|
|
||||||
// load-style-dict
|
|
||||||
var loadStyleDict = function(data) { _styleAtoms = sxOr(get(data, "a"), {});
|
|
||||||
_pseudoVariants = sxOr(get(data, "v"), {});
|
|
||||||
_responsiveBreakpoints = sxOr(get(data, "b"), {});
|
|
||||||
_styleKeyframes = sxOr(get(data, "k"), {});
|
|
||||||
_childSelectorPrefixes = sxOr(get(data, "c"), []);
|
|
||||||
_arbitraryPatterns = map(function(pair) { return {["re"]: compileRegex((String("^") + String(first(pair)) + String("$"))), ["tmpl"]: nth(pair, 1)}; }, sxOr(get(data, "p"), []));
|
|
||||||
return (_styleCache = {}); };
|
|
||||||
|
|
||||||
// split-variant
|
|
||||||
var splitVariant = function(atom) { return (function() {
|
|
||||||
var result = NIL;
|
|
||||||
{ var _c = keys(_responsiveBreakpoints); for (var _i = 0; _i < _c.length; _i++) { var bp = _c[_i]; if (isSxTruthy(isNil(result))) {
|
|
||||||
(function() {
|
|
||||||
var prefix = (String(bp) + String(":"));
|
|
||||||
return (isSxTruthy(startsWith(atom, prefix)) ? (function() {
|
|
||||||
var restAtom = slice(atom, len(prefix));
|
|
||||||
return (function() {
|
|
||||||
var innerMatch = NIL;
|
|
||||||
{ var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(innerMatch))) {
|
|
||||||
(function() {
|
|
||||||
var innerPrefix = (String(pv) + String(":"));
|
|
||||||
return (isSxTruthy(startsWith(restAtom, innerPrefix)) ? (innerMatch = [(String(bp) + String(":") + String(pv)), slice(restAtom, len(innerPrefix))]) : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return (result = sxOr(innerMatch, [bp, restAtom]));
|
|
||||||
})();
|
|
||||||
})() : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
if (isSxTruthy(isNil(result))) {
|
|
||||||
{ var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(result))) {
|
|
||||||
(function() {
|
|
||||||
var prefix = (String(pv) + String(":"));
|
|
||||||
return (isSxTruthy(startsWith(atom, prefix)) ? (result = [pv, slice(atom, len(prefix))]) : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
}
|
|
||||||
return sxOr(result, [NIL, atom]);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// resolve-atom
|
|
||||||
var resolveAtom = function(atom) { return (function() {
|
|
||||||
var decls = dictGet(_styleAtoms, atom);
|
|
||||||
return (isSxTruthy(!isNil(decls)) ? decls : (isSxTruthy(startsWith(atom, "animate-")) ? (function() {
|
|
||||||
var kfName = slice(atom, 8);
|
|
||||||
return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? (String("animation-name:") + String(kfName)) : NIL);
|
|
||||||
})() : (function() {
|
|
||||||
var matchResult = NIL;
|
|
||||||
{ var _c = _arbitraryPatterns; for (var _i = 0; _i < _c.length; _i++) { var pat = _c[_i]; if (isSxTruthy(isNil(matchResult))) {
|
|
||||||
(function() {
|
|
||||||
var m = regexMatch(get(pat, "re"), atom);
|
|
||||||
return (isSxTruthy(m) ? (matchResult = regexReplaceGroups(get(pat, "tmpl"), m)) : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return matchResult;
|
|
||||||
})()));
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// is-child-selector-atom?
|
|
||||||
var isChildSelectorAtom = function(atom) { return some(function(prefix) { return startsWith(atom, prefix); }, _childSelectorPrefixes); };
|
|
||||||
|
|
||||||
// hash-style
|
|
||||||
var hashStyle = function(input) { return fnv1aHash(input); };
|
|
||||||
|
|
||||||
// resolve-style
|
|
||||||
var resolveStyle = function(atoms) { return (function() {
|
|
||||||
var key = join("\\0", atoms);
|
|
||||||
return (function() {
|
|
||||||
var cached = dictGet(_styleCache, key);
|
|
||||||
return (isSxTruthy(!isNil(cached)) ? cached : (function() {
|
|
||||||
var baseDecls = [];
|
|
||||||
var mediaRules = [];
|
|
||||||
var pseudoRules = [];
|
|
||||||
var kfNeeded = [];
|
|
||||||
{ var _c = atoms; for (var _i = 0; _i < _c.length; _i++) { var a = _c[_i]; if (isSxTruthy(a)) {
|
|
||||||
(function() {
|
|
||||||
var clean = (isSxTruthy(startsWith(a, ":")) ? slice(a, 1) : a);
|
|
||||||
return (function() {
|
|
||||||
var parts = splitVariant(clean);
|
|
||||||
return (function() {
|
|
||||||
var variant = first(parts);
|
|
||||||
var base = nth(parts, 1);
|
|
||||||
var decls = resolveAtom(base);
|
|
||||||
return (isSxTruthy(decls) ? ((isSxTruthy(startsWith(base, "animate-")) ? (function() {
|
|
||||||
var kfName = slice(base, 8);
|
|
||||||
return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? append_b(kfNeeded, [kfName, dictGet(_styleKeyframes, kfName)]) : NIL);
|
|
||||||
})() : NIL), (isSxTruthy(isNil(variant)) ? (isSxTruthy(isChildSelectorAtom(base)) ? append_b(pseudoRules, [">:not(:first-child)", decls]) : append_b(baseDecls, decls)) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() {
|
|
||||||
var vparts = split(variant, ":");
|
|
||||||
var mediaPart = NIL;
|
|
||||||
var pseudoPart = NIL;
|
|
||||||
{ var _c = vparts; for (var _i = 0; _i < _c.length; _i++) { var vp = _c[_i]; (isSxTruthy(dictHas(_responsiveBreakpoints, vp)) ? (mediaPart = dictGet(_responsiveBreakpoints, vp)) : (isSxTruthy(dictHas(_pseudoVariants, vp)) ? (pseudoPart = dictGet(_pseudoVariants, vp)) : NIL)); } }
|
|
||||||
if (isSxTruthy(mediaPart)) {
|
|
||||||
mediaRules.push([mediaPart, decls]);
|
|
||||||
}
|
|
||||||
if (isSxTruthy(pseudoPart)) {
|
|
||||||
pseudoRules.push([pseudoPart, decls]);
|
|
||||||
}
|
|
||||||
return (isSxTruthy((isSxTruthy(isNil(mediaPart)) && isNil(pseudoPart))) ? append_b(baseDecls, decls) : NIL);
|
|
||||||
})())))) : NIL);
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return (function() {
|
|
||||||
var hashInput = join(";", baseDecls);
|
|
||||||
{ var _c = mediaRules; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } }
|
|
||||||
{ var _c = pseudoRules; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } }
|
|
||||||
{ var _c = kfNeeded; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } }
|
|
||||||
return (function() {
|
|
||||||
var cn = (String("sx-") + String(hashStyle(hashInput)));
|
|
||||||
var sv = makeStyleValue_(cn, join(";", baseDecls), mediaRules, pseudoRules, kfNeeded);
|
|
||||||
_styleCache[key] = sv;
|
|
||||||
injectStyleValue(sv, atoms);
|
|
||||||
return sv;
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
})());
|
|
||||||
})();
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// merge-style-values
|
|
||||||
var mergeStyleValues = function(styles) { return (isSxTruthy((len(styles) == 1)) ? first(styles) : (function() {
|
|
||||||
var allDecls = [];
|
|
||||||
var allMedia = [];
|
|
||||||
var allPseudo = [];
|
|
||||||
var allKf = [];
|
|
||||||
{ var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var sv = _c[_i]; if (isSxTruthy(styleValueDeclarations(sv))) {
|
|
||||||
allDecls.push(styleValueDeclarations(sv));
|
|
||||||
}
|
|
||||||
allMedia = concat(allMedia, styleValueMediaRules(sv));
|
|
||||||
allPseudo = concat(allPseudo, styleValuePseudoRules(sv));
|
|
||||||
allKf = concat(allKf, styleValueKeyframes_(sv)); } }
|
|
||||||
return (function() {
|
|
||||||
var hashInput = join(";", allDecls);
|
|
||||||
{ var _c = allMedia; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } }
|
|
||||||
{ var _c = allPseudo; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } }
|
|
||||||
{ var _c = allKf; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } }
|
|
||||||
return (function() {
|
|
||||||
var cn = (String("sx-") + String(hashStyle(hashInput)));
|
|
||||||
var merged = makeStyleValue_(cn, join(";", allDecls), allMedia, allPseudo, allKf);
|
|
||||||
injectStyleValue(merged, []);
|
|
||||||
return merged;
|
|
||||||
})();
|
|
||||||
})();
|
|
||||||
})()); };
|
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from boot ===
|
// === Transpiled from boot ===
|
||||||
|
|
||||||
// HEAD_HOIST_SELECTOR
|
// HEAD_HOIST_SELECTOR
|
||||||
@@ -2363,30 +2121,10 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
})();
|
})();
|
||||||
return setSxCompCookie(hash);
|
return setSxCompCookie(hash);
|
||||||
})());
|
})());
|
||||||
})(); };
|
|
||||||
|
|
||||||
// init-style-dict
|
|
||||||
var initStyleDict = function() { return (function() {
|
|
||||||
var scripts = queryStyleScripts();
|
|
||||||
return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "styles")) ? (markProcessed(s, "styles"), (function() {
|
|
||||||
var text = domTextContent(s);
|
|
||||||
var hash = domGetAttr(s, "data-hash");
|
|
||||||
return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? parseAndLoadStyleDict(text) : NIL) : (function() {
|
|
||||||
var hasInline = (isSxTruthy(text) && !isEmpty(trim(text)));
|
|
||||||
(function() {
|
|
||||||
var cachedHash = localStorageGet("sx-styles-hash");
|
|
||||||
return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo("styles: downloaded (cookie stale)")) : (function() {
|
|
||||||
var cached = localStorageGet("sx-styles-src");
|
|
||||||
return (isSxTruthy(cached) ? (parseAndLoadStyleDict(cached), logInfo((String("styles: cached (") + String(hash) + String(")")))) : (clearSxStylesCookie(), browserReload()));
|
|
||||||
})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-hash", hash), localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo((String("styles: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-styles-hash"), localStorageRemove("sx-styles-src"), clearSxStylesCookie(), browserReload())));
|
|
||||||
})();
|
|
||||||
return setSxStylesCookie(hash);
|
|
||||||
})());
|
|
||||||
})()) : NIL); }, scripts);
|
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// boot-init
|
// boot-init
|
||||||
var bootInit = function() { return (initCssTracking(), initStyleDict(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); };
|
var bootInit = function() { return (initCssTracking(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from deps (component dependency analysis) ===
|
// === Transpiled from deps (component dependency analysis) ===
|
||||||
@@ -3290,70 +3028,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeStyleValue_(cn, decls, media, pseudo, kf) {
|
|
||||||
return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function styleValueDeclarations(sv) { return sv.declarations; }
|
|
||||||
function styleValueMediaRules(sv) { return sv.mediaRules; }
|
|
||||||
function styleValuePseudoRules(sv) { return sv.pseudoRules; }
|
|
||||||
function styleValueKeyframes_(sv) { return sv.keyframes; }
|
|
||||||
|
|
||||||
function injectStyleValue(sv, atoms) {
|
|
||||||
if (_injectedStyles[sv.className]) return;
|
|
||||||
_injectedStyles[sv.className] = true;
|
|
||||||
|
|
||||||
if (!_hasDom) return;
|
|
||||||
var cssTarget = document.getElementById("sx-css");
|
|
||||||
if (!cssTarget) return;
|
|
||||||
|
|
||||||
var rules = [];
|
|
||||||
// Child-selector atoms are now routed to pseudoRules by the resolver
|
|
||||||
// with selector ">:not(:first-child)", so base declarations are always
|
|
||||||
// applied directly to the class.
|
|
||||||
if (sv.declarations) {
|
|
||||||
rules.push("." + sv.className + "{" + sv.declarations + "}");
|
|
||||||
}
|
|
||||||
for (var pi = 0; pi < sv.pseudoRules.length; pi++) {
|
|
||||||
var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1];
|
|
||||||
if (sel.indexOf("&") >= 0) {
|
|
||||||
rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}");
|
|
||||||
} else {
|
|
||||||
rules.push("." + sv.className + sel + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var mi = 0; mi < sv.mediaRules.length; mi++) {
|
|
||||||
rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}");
|
|
||||||
}
|
|
||||||
for (var ki = 0; ki < sv.keyframes.length; ki++) {
|
|
||||||
rules.push(sv.keyframes[ki][1]);
|
|
||||||
}
|
|
||||||
cssTarget.textContent += rules.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace stub css primitive with real CSSX implementation
|
|
||||||
PRIMITIVES["css"] = function() {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return resolveStyle(atoms);
|
|
||||||
};
|
|
||||||
|
|
||||||
PRIMITIVES["merge-styles"] = function() {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
return mergeStyleValues(valid);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -3410,12 +3084,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
r.querySelectorAll('script[type="text/sx"]'));
|
r.querySelectorAll('script[type="text/sx"]'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryStyleScripts() {
|
|
||||||
if (!_hasDom) return [];
|
|
||||||
return Array.prototype.slice.call(
|
|
||||||
document.querySelectorAll('script[type="text/sx-styles"]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- localStorage ---
|
// --- localStorage ---
|
||||||
|
|
||||||
function localStorageGet(key) {
|
function localStorageGet(key) {
|
||||||
@@ -3441,14 +3109,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSxStylesCookie(hash) {
|
|
||||||
if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSxStylesCookie() {
|
|
||||||
if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Env helpers ---
|
// --- Env helpers ---
|
||||||
|
|
||||||
function parseEnvAttr(el) {
|
function parseEnvAttr(el) {
|
||||||
@@ -3493,12 +3153,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAndLoadStyleDict(text) {
|
|
||||||
try { loadStyleDict(JSON.parse(text)); }
|
|
||||||
catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Post-transpilation fixups
|
// Post-transpilation fixups
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -3675,7 +3329,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
transitiveIoRefs: transitiveIoRefs,
|
transitiveIoRefs: transitiveIoRefs,
|
||||||
computeAllIoRefs: computeAllIoRefs,
|
computeAllIoRefs: computeAllIoRefs,
|
||||||
componentPure_p: componentPure_p,
|
componentPure_p: componentPure_p,
|
||||||
_version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,23 +63,12 @@
|
|||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
|
|
||||||
/** CSSX StyleValue — generated CSS class with rules. */
|
|
||||||
function StyleValue(className, declarations, mediaRules, pseudoRules, keyframes) {
|
|
||||||
this.className = className;
|
|
||||||
this.declarations = declarations || "";
|
|
||||||
this.mediaRules = mediaRules || [];
|
|
||||||
this.pseudoRules = pseudoRules || [];
|
|
||||||
this.keyframes = keyframes || [];
|
|
||||||
}
|
|
||||||
StyleValue.prototype._styleValue = true;
|
|
||||||
|
|
||||||
function isSym(x) { return x && x._sym === true; }
|
function isSym(x) { return x && x._sym === true; }
|
||||||
function isKw(x) { return x && x._kw === true; }
|
function isKw(x) { return x && x._kw === true; }
|
||||||
function isLambda(x) { return x && x._lambda === true; }
|
function isLambda(x) { return x && x._lambda === true; }
|
||||||
function isComponent(x) { return x && x._component === true; }
|
function isComponent(x) { return x && x._component === true; }
|
||||||
function isMacro(x) { return x && x._macro === true; }
|
function isMacro(x) { return x && x._macro === true; }
|
||||||
function isRaw(x) { return x && x._raw === true; }
|
function isRaw(x) { return x && x._raw === true; }
|
||||||
function isStyleValue(x) { return x && x._styleValue === true; }
|
|
||||||
|
|
||||||
// --- Parser ---
|
// --- Parser ---
|
||||||
|
|
||||||
@@ -416,227 +405,6 @@
|
|||||||
return String(s).split(",").map(function(x) { return x.trim(); }).filter(function(x) { return x; });
|
return String(s).split(",").map(function(x) { return x.trim(); }).filter(function(x) { return x; });
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- CSSX Style Dictionary + Resolver ---
|
|
||||||
|
|
||||||
var _styleAtoms = {}; // atom → CSS declarations
|
|
||||||
var _pseudoVariants = {}; // variant → CSS pseudo-selector
|
|
||||||
var _responsiveBreakpoints = {}; // variant → media query
|
|
||||||
var _styleKeyframes = {}; // name → @keyframes rule
|
|
||||||
var _arbitraryPatterns = []; // [{re: RegExp, tmpl: string}, ...]
|
|
||||||
var _childSelectorPrefixes = []; // ["space-x-", "space-y-", ...]
|
|
||||||
var _styleCache = {}; // atoms-key → StyleValue
|
|
||||||
var _injectedStyles = {}; // className → true (already in <style>)
|
|
||||||
|
|
||||||
function _loadStyleDict(data) {
|
|
||||||
_styleAtoms = data.a || {};
|
|
||||||
_pseudoVariants = data.v || {};
|
|
||||||
_responsiveBreakpoints = data.b || {};
|
|
||||||
_styleKeyframes = data.k || {};
|
|
||||||
_childSelectorPrefixes = data.c || [];
|
|
||||||
_arbitraryPatterns = [];
|
|
||||||
var pats = data.p || [];
|
|
||||||
for (var i = 0; i < pats.length; i++) {
|
|
||||||
_arbitraryPatterns.push({ re: new RegExp("^" + pats[i][0] + "$"), tmpl: pats[i][1] });
|
|
||||||
}
|
|
||||||
_styleCache = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function _splitVariant(atom) {
|
|
||||||
// Check responsive prefix first
|
|
||||||
for (var bp in _responsiveBreakpoints) {
|
|
||||||
var prefix = bp + ":";
|
|
||||||
if (atom.indexOf(prefix) === 0) {
|
|
||||||
var rest = atom.substring(prefix.length);
|
|
||||||
for (var pv in _pseudoVariants) {
|
|
||||||
var inner = pv + ":";
|
|
||||||
if (rest.indexOf(inner) === 0) return [bp + ":" + pv, rest.substring(inner.length)];
|
|
||||||
}
|
|
||||||
return [bp, rest];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var pv2 in _pseudoVariants) {
|
|
||||||
var prefix2 = pv2 + ":";
|
|
||||||
if (atom.indexOf(prefix2) === 0) return [pv2, atom.substring(prefix2.length)];
|
|
||||||
}
|
|
||||||
return [null, atom];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _resolveAtom(atom) {
|
|
||||||
var decls = _styleAtoms[atom];
|
|
||||||
if (decls !== undefined) return decls;
|
|
||||||
// Dynamic keyframes: animate-{name} → animation-name:{name}
|
|
||||||
if (atom.indexOf("animate-") === 0) {
|
|
||||||
var kfName = atom.substring(8);
|
|
||||||
if (_styleKeyframes[kfName]) return "animation-name:" + kfName;
|
|
||||||
}
|
|
||||||
for (var i = 0; i < _arbitraryPatterns.length; i++) {
|
|
||||||
var m = atom.match(_arbitraryPatterns[i].re);
|
|
||||||
if (m) {
|
|
||||||
var result = _arbitraryPatterns[i].tmpl;
|
|
||||||
for (var j = 1; j < m.length; j++) result = result.replace("{" + (j - 1) + "}", m[j]);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _isChildSelectorAtom(atom) {
|
|
||||||
for (var i = 0; i < _childSelectorPrefixes.length; i++) {
|
|
||||||
if (atom.indexOf(_childSelectorPrefixes[i]) === 0) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** SHA-256 hash (first 6 hex chars) for content-addressed class names. */
|
|
||||||
function _hashStyle(input) {
|
|
||||||
// Simple FNV-1a 32-bit hash — fast, deterministic, good distribution
|
|
||||||
var h = 0x811c9dc5;
|
|
||||||
for (var i = 0; i < input.length; i++) {
|
|
||||||
h ^= input.charCodeAt(i);
|
|
||||||
h = (h * 0x01000193) >>> 0;
|
|
||||||
}
|
|
||||||
return h.toString(16).padStart(8, "0").substring(0, 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _resolveStyle(atoms) {
|
|
||||||
var key = atoms.join("\0");
|
|
||||||
if (_styleCache[key]) return _styleCache[key];
|
|
||||||
|
|
||||||
var baseDecls = [], mediaRules = [], pseudoRules = [], kfNeeded = [];
|
|
||||||
for (var i = 0; i < atoms.length; i++) {
|
|
||||||
var a = atoms[i];
|
|
||||||
if (!a) continue;
|
|
||||||
if (a.charAt(0) === ":") a = a.substring(1);
|
|
||||||
|
|
||||||
var parts = _splitVariant(a);
|
|
||||||
var variant = parts[0], base = parts[1];
|
|
||||||
var decls = _resolveAtom(base);
|
|
||||||
if (!decls) continue;
|
|
||||||
|
|
||||||
// Check keyframes
|
|
||||||
if (base.indexOf("animate-") === 0) {
|
|
||||||
var kfName = base.substring(8);
|
|
||||||
if (_styleKeyframes[kfName]) kfNeeded.push([kfName, _styleKeyframes[kfName]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variant === null) {
|
|
||||||
baseDecls.push(decls);
|
|
||||||
} else if (_responsiveBreakpoints[variant]) {
|
|
||||||
mediaRules.push([_responsiveBreakpoints[variant], decls]);
|
|
||||||
} else if (_pseudoVariants[variant]) {
|
|
||||||
pseudoRules.push([_pseudoVariants[variant], decls]);
|
|
||||||
} else {
|
|
||||||
// Compound variant: "sm:hover" → split
|
|
||||||
var vparts = variant.split(":");
|
|
||||||
var mediaPart = null, pseudoPart = null;
|
|
||||||
for (var vi = 0; vi < vparts.length; vi++) {
|
|
||||||
if (_responsiveBreakpoints[vparts[vi]]) mediaPart = _responsiveBreakpoints[vparts[vi]];
|
|
||||||
else if (_pseudoVariants[vparts[vi]]) pseudoPart = _pseudoVariants[vparts[vi]];
|
|
||||||
}
|
|
||||||
if (mediaPart) mediaRules.push([mediaPart, decls]);
|
|
||||||
if (pseudoPart) pseudoRules.push([pseudoPart, decls]);
|
|
||||||
if (!mediaPart && !pseudoPart) baseDecls.push(decls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build hash input
|
|
||||||
var hashInput = baseDecls.join(";");
|
|
||||||
for (var mi = 0; mi < mediaRules.length; mi++) hashInput += "@" + mediaRules[mi][0] + "{" + mediaRules[mi][1] + "}";
|
|
||||||
for (var pi = 0; pi < pseudoRules.length; pi++) hashInput += pseudoRules[pi][0] + "{" + pseudoRules[pi][1] + "}";
|
|
||||||
for (var ki = 0; ki < kfNeeded.length; ki++) hashInput += kfNeeded[ki][1];
|
|
||||||
|
|
||||||
var cn = "sx-" + _hashStyle(hashInput);
|
|
||||||
var sv = new StyleValue(cn, baseDecls.join(";"), mediaRules, pseudoRules, kfNeeded);
|
|
||||||
_styleCache[key] = sv;
|
|
||||||
|
|
||||||
// Inject CSS rules into <style id="sx-css">
|
|
||||||
_injectStyleValue(sv, atoms);
|
|
||||||
return sv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _injectStyleValue(sv, atoms) {
|
|
||||||
if (_injectedStyles[sv.className]) return;
|
|
||||||
_injectedStyles[sv.className] = true;
|
|
||||||
|
|
||||||
var cssTarget = document.getElementById("sx-css");
|
|
||||||
if (!cssTarget) return;
|
|
||||||
|
|
||||||
var rules = [];
|
|
||||||
if (sv.declarations) {
|
|
||||||
// Check if any atoms need child selectors
|
|
||||||
var hasChild = false;
|
|
||||||
if (atoms) {
|
|
||||||
for (var ai = 0; ai < atoms.length; ai++) {
|
|
||||||
if (_isChildSelectorAtom(atoms[ai])) { hasChild = true; break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasChild) {
|
|
||||||
rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}");
|
|
||||||
} else {
|
|
||||||
rules.push("." + sv.className + "{" + sv.declarations + "}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var pi = 0; pi < sv.pseudoRules.length; pi++) {
|
|
||||||
var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1];
|
|
||||||
if (sel.indexOf("&") >= 0) {
|
|
||||||
rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}");
|
|
||||||
} else {
|
|
||||||
rules.push("." + sv.className + sel + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var mi = 0; mi < sv.mediaRules.length; mi++) {
|
|
||||||
rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}");
|
|
||||||
}
|
|
||||||
for (var ki = 0; ki < sv.keyframes.length; ki++) {
|
|
||||||
rules.push(sv.keyframes[ki][1]);
|
|
||||||
}
|
|
||||||
cssTarget.textContent += rules.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _mergeStyleValues(styles) {
|
|
||||||
if (styles.length === 1) return styles[0];
|
|
||||||
var allDecls = [], allMedia = [], allPseudo = [], allKf = [];
|
|
||||||
var allAtoms = [];
|
|
||||||
for (var i = 0; i < styles.length; i++) {
|
|
||||||
var sv = styles[i];
|
|
||||||
if (sv.declarations) allDecls.push(sv.declarations);
|
|
||||||
allMedia = allMedia.concat(sv.mediaRules);
|
|
||||||
allPseudo = allPseudo.concat(sv.pseudoRules);
|
|
||||||
allKf = allKf.concat(sv.keyframes);
|
|
||||||
}
|
|
||||||
var hashInput = allDecls.join(";");
|
|
||||||
for (var mi = 0; mi < allMedia.length; mi++) hashInput += "@" + allMedia[mi][0] + "{" + allMedia[mi][1] + "}";
|
|
||||||
for (var pi = 0; pi < allPseudo.length; pi++) hashInput += allPseudo[pi][0] + "{" + allPseudo[pi][1] + "}";
|
|
||||||
for (var ki = 0; ki < allKf.length; ki++) hashInput += allKf[ki][1];
|
|
||||||
var cn = "sx-" + _hashStyle(hashInput);
|
|
||||||
var merged = new StyleValue(cn, allDecls.join(";"), allMedia, allPseudo, allKf);
|
|
||||||
_injectStyleValue(merged, allAtoms);
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
|
|
||||||
// css primitive: (css :flex :gap-4 :hover:bg-sky-200) → StyleValue
|
|
||||||
PRIMITIVES["css"] = function () {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return _resolveStyle(atoms);
|
|
||||||
};
|
|
||||||
|
|
||||||
// merge-styles: combine multiple StyleValues
|
|
||||||
PRIMITIVES["merge-styles"] = function () {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
return _mergeStyleValues(valid);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Evaluator ---
|
// --- Evaluator ---
|
||||||
|
|
||||||
/** Unwrap thunks by re-entering the evaluator until we get an actual value. */
|
/** Unwrap thunks by re-entering the evaluator until we get an actual value. */
|
||||||
@@ -892,30 +660,6 @@
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["defkeyframes"] = function (expr, env) {
|
|
||||||
var kfName = expr[1].name;
|
|
||||||
var steps = [];
|
|
||||||
for (var i = 2; i < expr.length; i++) {
|
|
||||||
var step = expr[i];
|
|
||||||
var selector = isSym(step[0]) ? step[0].name : String(step[0]);
|
|
||||||
var body = sxEval(step[1], env);
|
|
||||||
var decls = isStyleValue(body) ? body.declarations : String(body);
|
|
||||||
steps.push(selector + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
var kfRule = "@keyframes " + kfName + "{" + steps.join("") + "}";
|
|
||||||
|
|
||||||
// Register in keyframes dict for animate-{name} lookup
|
|
||||||
_styleKeyframes[kfName] = kfRule;
|
|
||||||
_styleCache = {}; // Clear cache so new keyframes are picked up
|
|
||||||
|
|
||||||
// Build a StyleValue for the animation
|
|
||||||
var cn = "sx-" + _hashStyle(kfRule);
|
|
||||||
var sv = new StyleValue(cn, "animation-name:" + kfName, [], [], [[kfName, kfRule]]);
|
|
||||||
_injectStyleValue(sv, []);
|
|
||||||
env[kfName] = sv;
|
|
||||||
return sv;
|
|
||||||
};
|
|
||||||
|
|
||||||
SPECIAL_FORMS["defcomp"] = function (expr, env) {
|
SPECIAL_FORMS["defcomp"] = function (expr, env) {
|
||||||
var nameSym = expr[1];
|
var nameSym = expr[1];
|
||||||
var compName = nameSym.name.replace(/^~/, "");
|
var compName = nameSym.name.replace(/^~/, "");
|
||||||
@@ -1209,7 +953,6 @@
|
|||||||
|
|
||||||
RENDER_FORMS["define"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
RENDER_FORMS["define"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||||
RENDER_FORMS["defstyle"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
RENDER_FORMS["defstyle"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||||
RENDER_FORMS["defkeyframes"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
|
||||||
RENDER_FORMS["defcomp"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
RENDER_FORMS["defcomp"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||||
RENDER_FORMS["defmacro"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
RENDER_FORMS["defmacro"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||||
RENDER_FORMS["defhandler"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
RENDER_FORMS["defhandler"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||||
@@ -1413,7 +1156,6 @@
|
|||||||
? document.createElementNS(ns, tag)
|
? document.createElementNS(ns, tag)
|
||||||
: (SVG_TAGS[tag] ? document.createElementNS(SVG_NS, tag) : document.createElement(tag));
|
: (SVG_TAGS[tag] ? document.createElementNS(SVG_NS, tag) : document.createElement(tag));
|
||||||
|
|
||||||
var extraClass = null;
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
while (i < args.length) {
|
while (i < args.length) {
|
||||||
var arg = args[i];
|
var arg = args[i];
|
||||||
@@ -1422,11 +1164,6 @@
|
|||||||
var attrVal = sxEval(args[i + 1], env);
|
var attrVal = sxEval(args[i + 1], env);
|
||||||
i += 2;
|
i += 2;
|
||||||
if (isNil(attrVal) || attrVal === false) continue;
|
if (isNil(attrVal) || attrVal === false) continue;
|
||||||
// :style StyleValue → convert to class
|
|
||||||
if (attrName === "style" && isStyleValue(attrVal)) {
|
|
||||||
extraClass = attrVal.className;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (BOOLEAN_ATTRS[attrName]) {
|
if (BOOLEAN_ATTRS[attrName]) {
|
||||||
if (attrVal) el.setAttribute(attrName, "");
|
if (attrVal) el.setAttribute(attrName, "");
|
||||||
} else if (attrVal === true) {
|
} else if (attrVal === true) {
|
||||||
@@ -1443,12 +1180,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge StyleValue class into element's class attribute
|
|
||||||
if (extraClass) {
|
|
||||||
var existing = el.getAttribute("class");
|
|
||||||
el.setAttribute("class", existing ? existing + " " + extraClass : extraClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1770,7 +1501,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
// For testing / sx-test.js
|
// For testing / sx-test.js
|
||||||
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML, StyleValue: StyleValue },
|
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
|
||||||
_eval: sxEval,
|
_eval: sxEval,
|
||||||
_expandMacro: expandMacro,
|
_expandMacro: expandMacro,
|
||||||
_callLambda: function (fn, args, env) { return trampoline(callLambda(fn, args, env)); },
|
_callLambda: function (fn, args, env) { return trampoline(callLambda(fn, args, env)); },
|
||||||
@@ -3116,79 +2847,10 @@
|
|||||||
document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- sx-styles-hash cookie helpers ---
|
|
||||||
|
|
||||||
function _setSxStylesCookie(hash) {
|
|
||||||
document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
function _clearSxStylesCookie() {
|
|
||||||
document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
function _initStyleDict() {
|
|
||||||
var scripts = document.querySelectorAll('script[type="text/sx-styles"]');
|
|
||||||
for (var i = 0; i < scripts.length; i++) {
|
|
||||||
var s = scripts[i];
|
|
||||||
if (s._sxProcessed) continue;
|
|
||||||
s._sxProcessed = true;
|
|
||||||
|
|
||||||
var text = s.textContent;
|
|
||||||
var hash = s.getAttribute("data-hash");
|
|
||||||
if (!hash) {
|
|
||||||
if (text && text.trim()) {
|
|
||||||
try { _loadStyleDict(JSON.parse(text)); } catch (e) { console.warn("[sx.js] style dict parse error", e); }
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasInline = text && text.trim();
|
|
||||||
try {
|
|
||||||
var cachedHash = localStorage.getItem("sx-styles-hash");
|
|
||||||
if (cachedHash === hash) {
|
|
||||||
if (hasInline) {
|
|
||||||
localStorage.setItem("sx-styles-src", text);
|
|
||||||
_loadStyleDict(JSON.parse(text));
|
|
||||||
console.log("[sx.js] styles: downloaded (cookie stale)");
|
|
||||||
} else {
|
|
||||||
var cached = localStorage.getItem("sx-styles-src");
|
|
||||||
if (cached) {
|
|
||||||
_loadStyleDict(JSON.parse(cached));
|
|
||||||
console.log("[sx.js] styles: cached (" + hash + ")");
|
|
||||||
} else {
|
|
||||||
_clearSxStylesCookie();
|
|
||||||
location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (hasInline) {
|
|
||||||
localStorage.setItem("sx-styles-hash", hash);
|
|
||||||
localStorage.setItem("sx-styles-src", text);
|
|
||||||
_loadStyleDict(JSON.parse(text));
|
|
||||||
console.log("[sx.js] styles: downloaded (" + hash + ")");
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem("sx-styles-hash");
|
|
||||||
localStorage.removeItem("sx-styles-src");
|
|
||||||
_clearSxStylesCookie();
|
|
||||||
location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (hasInline) {
|
|
||||||
try { _loadStyleDict(JSON.parse(text)); } catch (e2) { console.warn("[sx.js] style dict parse error", e2); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_setSxStylesCookie(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
var init = function () {
|
var init = function () {
|
||||||
console.log("[sx.js] v" + Sx.VERSION + " init");
|
console.log("[sx.js] v" + Sx.VERSION + " init");
|
||||||
_initCssTracking();
|
_initCssTracking();
|
||||||
_initStyleDict();
|
|
||||||
Sx.processScripts();
|
Sx.processScripts();
|
||||||
Sx.hydrate();
|
Sx.hydrate();
|
||||||
SxEngine.process();
|
SxEngine.process();
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import contextvars
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .types import Component, Keyword, Lambda, Macro, NIL, StyleValue, Symbol
|
from .types import Component, Keyword, Lambda, Macro, NIL, Symbol
|
||||||
|
|
||||||
# When True, _aser expands known components server-side instead of serializing
|
# When True, _aser expands known components server-side instead of serializing
|
||||||
# them for client rendering. Set during page slot evaluation so Python-only
|
# them for client rendering. Set during page slot evaluation so Python-only
|
||||||
@@ -425,11 +425,6 @@ async def _asf_defstyle(expr, env, ctx):
|
|||||||
return _sf_defstyle(expr, env)
|
return _sf_defstyle(expr, env)
|
||||||
|
|
||||||
|
|
||||||
async def _asf_defkeyframes(expr, env, ctx):
|
|
||||||
from .evaluator import _sf_defkeyframes
|
|
||||||
return _sf_defkeyframes(expr, env)
|
|
||||||
|
|
||||||
|
|
||||||
async def _asf_defmacro(expr, env, ctx):
|
async def _asf_defmacro(expr, env, ctx):
|
||||||
from .evaluator import _sf_defmacro
|
from .evaluator import _sf_defmacro
|
||||||
return _sf_defmacro(expr, env)
|
return _sf_defmacro(expr, env)
|
||||||
@@ -568,7 +563,6 @@ _ASYNC_SPECIAL_FORMS: dict[str, Any] = {
|
|||||||
"fn": _asf_lambda,
|
"fn": _asf_lambda,
|
||||||
"define": _asf_define,
|
"define": _asf_define,
|
||||||
"defstyle": _asf_defstyle,
|
"defstyle": _asf_defstyle,
|
||||||
"defkeyframes": _asf_defkeyframes,
|
|
||||||
"defcomp": _asf_defcomp,
|
"defcomp": _asf_defcomp,
|
||||||
"defmacro": _asf_defmacro,
|
"defmacro": _asf_defmacro,
|
||||||
"defhandler": _asf_defhandler,
|
"defhandler": _asf_defhandler,
|
||||||
@@ -884,18 +878,6 @@ async def _arender_element(
|
|||||||
children.append(arg)
|
children.append(arg)
|
||||||
i += 1
|
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)
|
|
||||||
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"]
|
|
||||||
|
|
||||||
class_val = attrs.get("class")
|
class_val = attrs.get("class")
|
||||||
if class_val is not None and class_val is not NIL and class_val is not False:
|
if class_val is not None and class_val is not NIL and class_val is not False:
|
||||||
collector = css_class_collector.get(None)
|
collector = css_class_collector.get(None)
|
||||||
@@ -1120,7 +1102,6 @@ _ASYNC_RENDER_FORMS: dict[str, Any] = {
|
|||||||
"do": _arsf_begin,
|
"do": _arsf_begin,
|
||||||
"define": _arsf_define,
|
"define": _arsf_define,
|
||||||
"defstyle": _arsf_define,
|
"defstyle": _arsf_define,
|
||||||
"defkeyframes": _arsf_define,
|
|
||||||
"defcomp": _arsf_define,
|
"defcomp": _arsf_define,
|
||||||
"defmacro": _arsf_define,
|
"defmacro": _arsf_define,
|
||||||
"defhandler": _arsf_define,
|
"defhandler": _arsf_define,
|
||||||
@@ -1441,41 +1422,35 @@ async def _aser_call(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
parts = [name]
|
parts = [name]
|
||||||
extra_class: str | None = None # from :style StyleValue conversion
|
extra_class: str | None = None
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(args):
|
while i < len(args):
|
||||||
arg = args[i]
|
arg = args[i]
|
||||||
if isinstance(arg, Keyword) and i + 1 < len(args):
|
if isinstance(arg, Keyword) and i + 1 < len(args):
|
||||||
val = await _aser(args[i + 1], env, ctx)
|
val = await _aser(args[i + 1], env, ctx)
|
||||||
if val is not NIL and val is not None:
|
if val is not NIL and val is not None:
|
||||||
# :style StyleValue → convert to :class and register CSS
|
parts.append(f":{arg.name}")
|
||||||
if arg.name == "style" and isinstance(val, StyleValue):
|
# Plain list → serialize for the client.
|
||||||
from .css_registry import register_generated_rule
|
# Rendered items (SxExpr) → wrap in (<> ...) fragment.
|
||||||
register_generated_rule(val)
|
# Data items (dicts, strings, numbers) → (list ...)
|
||||||
extra_class = val.class_name
|
# so the client gets an iterable array, not a
|
||||||
else:
|
# DocumentFragment that breaks map/filter.
|
||||||
parts.append(f":{arg.name}")
|
if isinstance(val, list):
|
||||||
# Plain list → serialize for the client.
|
live = [v for v in val
|
||||||
# Rendered items (SxExpr) → wrap in (<> ...) fragment.
|
if v is not NIL and v is not None]
|
||||||
# Data items (dicts, strings, numbers) → (list ...)
|
items = [serialize(v) for v in live]
|
||||||
# so the client gets an iterable array, not a
|
if not items:
|
||||||
# DocumentFragment that breaks map/filter.
|
parts.append("nil")
|
||||||
if isinstance(val, list):
|
elif any(isinstance(v, SxExpr) for v in live):
|
||||||
live = [v for v in val
|
parts.append(
|
||||||
if v is not NIL and v is not None]
|
"(<> " + " ".join(items) + ")"
|
||||||
items = [serialize(v) for v in live]
|
)
|
||||||
if not items:
|
|
||||||
parts.append("nil")
|
|
||||||
elif any(isinstance(v, SxExpr) for v in live):
|
|
||||||
parts.append(
|
|
||||||
"(<> " + " ".join(items) + ")"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
parts.append(
|
|
||||||
"(list " + " ".join(items) + ")"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
parts.append(serialize(val))
|
parts.append(
|
||||||
|
"(list " + " ".join(items) + ")"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
parts.append(serialize(val))
|
||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
result = await _aser(arg, env, ctx)
|
result = await _aser(arg, env, ctx)
|
||||||
@@ -1725,7 +1700,6 @@ _ASER_FORMS: dict[str, Any] = {
|
|||||||
"fn": _assf_lambda,
|
"fn": _assf_lambda,
|
||||||
"define": _assf_define,
|
"define": _assf_define,
|
||||||
"defstyle": _assf_define,
|
"defstyle": _assf_define,
|
||||||
"defkeyframes": _assf_define,
|
|
||||||
"defcomp": _assf_define,
|
"defcomp": _assf_define,
|
||||||
"defmacro": _assf_define,
|
"defmacro": _assf_define,
|
||||||
"defhandler": _assf_define,
|
"defhandler": _assf_define,
|
||||||
|
|||||||
@@ -94,10 +94,10 @@ def validate_helper(service: str, name: str) -> None:
|
|||||||
def validate_boundary_value(value: Any, context: str = "") -> None:
|
def validate_boundary_value(value: Any, context: str = "") -> None:
|
||||||
"""Validate that a value is an allowed SX boundary type.
|
"""Validate that a value is an allowed SX boundary type.
|
||||||
|
|
||||||
Allowed: int, float, str, bool, None/NIL, list, dict, SxExpr, StyleValue.
|
Allowed: int, float, str, bool, None/NIL, list, dict, SxExpr.
|
||||||
NOT allowed: datetime, ORM models, Quart objects, raw callables.
|
NOT allowed: datetime, ORM models, Quart objects, raw callables.
|
||||||
"""
|
"""
|
||||||
from .types import NIL, StyleValue
|
from .types import NIL
|
||||||
from .parser import SxExpr
|
from .parser import SxExpr
|
||||||
|
|
||||||
if value is None or value is NIL:
|
if value is None or value is NIL:
|
||||||
@@ -106,8 +106,6 @@ def validate_boundary_value(value: Any, context: str = "") -> None:
|
|||||||
return
|
return
|
||||||
if isinstance(value, SxExpr):
|
if isinstance(value, SxExpr):
|
||||||
return
|
return
|
||||||
if isinstance(value, StyleValue):
|
|
||||||
return
|
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
for item in value:
|
for item in value:
|
||||||
validate_boundary_value(item, context)
|
validate_boundary_value(item, context)
|
||||||
|
|||||||
@@ -147,48 +147,6 @@ def scan_classes_from_sx(source: str) -> set[str]:
|
|||||||
return classes
|
return classes
|
||||||
|
|
||||||
|
|
||||||
def register_generated_rule(style_val: Any) -> None:
|
|
||||||
"""Register a generated StyleValue's CSS rules in the registry.
|
|
||||||
|
|
||||||
This allows generated class names (``sx-a3f2c1``) to flow through
|
|
||||||
the existing ``lookup_rules()`` → ``SX-Css`` delta pipeline.
|
|
||||||
"""
|
|
||||||
from .style_dict import CHILD_SELECTOR_ATOMS
|
|
||||||
cn = style_val.class_name
|
|
||||||
if cn in _REGISTRY:
|
|
||||||
return # already registered
|
|
||||||
|
|
||||||
parts: list[str] = []
|
|
||||||
|
|
||||||
# Base declarations
|
|
||||||
if style_val.declarations:
|
|
||||||
parts.append(f".{cn}{{{style_val.declarations}}}")
|
|
||||||
|
|
||||||
# Pseudo-class rules
|
|
||||||
for sel, decls in style_val.pseudo_rules:
|
|
||||||
if sel.startswith("::"):
|
|
||||||
parts.append(f".{cn}{sel}{{{decls}}}")
|
|
||||||
elif "&" in sel:
|
|
||||||
# group-hover pattern: ":is(.group:hover) &" → .group:hover .sx-abc
|
|
||||||
expanded = sel.replace("&", f".{cn}")
|
|
||||||
parts.append(f"{expanded}{{{decls}}}")
|
|
||||||
else:
|
|
||||||
parts.append(f".{cn}{sel}{{{decls}}}")
|
|
||||||
|
|
||||||
# Media-query rules
|
|
||||||
for query, decls in style_val.media_rules:
|
|
||||||
parts.append(f"@media {query}{{.{cn}{{{decls}}}}}")
|
|
||||||
|
|
||||||
# Keyframes
|
|
||||||
for _name, kf_rule in style_val.keyframes:
|
|
||||||
parts.append(kf_rule)
|
|
||||||
|
|
||||||
rule_text = "".join(parts)
|
|
||||||
order = len(_RULE_ORDER) + 10000 # after all tw.css rules
|
|
||||||
_REGISTRY[cn] = rule_text
|
|
||||||
_RULE_ORDER[cn] = order
|
|
||||||
|
|
||||||
|
|
||||||
def registry_loaded() -> bool:
|
def registry_loaded() -> bool:
|
||||||
"""True if the registry has been populated."""
|
"""True if the registry has been populated."""
|
||||||
return bool(_REGISTRY)
|
return bool(_REGISTRY)
|
||||||
|
|||||||
@@ -493,9 +493,9 @@ def _sf_define(expr: list, env: dict) -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def _sf_defstyle(expr: list, env: dict) -> Any:
|
def _sf_defstyle(expr: list, env: dict) -> Any:
|
||||||
"""``(defstyle card-base (css :rounded-xl :bg-white :shadow))``
|
"""``(defstyle card-base ...)``
|
||||||
|
|
||||||
Evaluates body → StyleValue, binds to name in env.
|
Evaluates body and binds to name in env.
|
||||||
"""
|
"""
|
||||||
if len(expr) < 3:
|
if len(expr) < 3:
|
||||||
raise EvalError("defstyle requires name and body")
|
raise EvalError("defstyle requires name and body")
|
||||||
@@ -507,63 +507,6 @@ def _sf_defstyle(expr: list, env: dict) -> Any:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _sf_defkeyframes(expr: list, env: dict) -> Any:
|
|
||||||
"""``(defkeyframes fade-in (from (css :opacity-0)) (to (css :opacity-100)))``
|
|
||||||
|
|
||||||
Builds @keyframes rule from steps, registers it, and binds the animation.
|
|
||||||
"""
|
|
||||||
from .types import StyleValue
|
|
||||||
from .css_registry import register_generated_rule
|
|
||||||
from .style_dict import KEYFRAMES
|
|
||||||
|
|
||||||
if len(expr) < 3:
|
|
||||||
raise EvalError("defkeyframes requires name and at least one step")
|
|
||||||
name_sym = expr[1]
|
|
||||||
if not isinstance(name_sym, Symbol):
|
|
||||||
raise EvalError(f"defkeyframes name must be symbol, got {type(name_sym).__name__}")
|
|
||||||
|
|
||||||
kf_name = name_sym.name
|
|
||||||
|
|
||||||
# Build @keyframes rule from steps
|
|
||||||
steps: list[str] = []
|
|
||||||
for step_expr in expr[2:]:
|
|
||||||
if not isinstance(step_expr, list) or len(step_expr) < 2:
|
|
||||||
raise EvalError("defkeyframes step must be (selector (css ...))")
|
|
||||||
selector = step_expr[0]
|
|
||||||
if isinstance(selector, Symbol):
|
|
||||||
selector = selector.name
|
|
||||||
else:
|
|
||||||
selector = str(selector)
|
|
||||||
body = _trampoline(_eval(step_expr[1], env))
|
|
||||||
if isinstance(body, StyleValue):
|
|
||||||
decls = body.declarations
|
|
||||||
elif isinstance(body, str):
|
|
||||||
decls = body
|
|
||||||
else:
|
|
||||||
raise EvalError(f"defkeyframes step body must be css/string, got {type(body).__name__}")
|
|
||||||
steps.append(f"{selector}{{{decls}}}")
|
|
||||||
|
|
||||||
kf_rule = f"@keyframes {kf_name}{{{' '.join(steps)}}}"
|
|
||||||
|
|
||||||
# Register in KEYFRAMES so animate-{name} works
|
|
||||||
KEYFRAMES[kf_name] = kf_rule
|
|
||||||
# Clear resolver cache so new keyframes are picked up
|
|
||||||
from .style_resolver import _resolve_cached
|
|
||||||
_resolve_cached.cache_clear()
|
|
||||||
|
|
||||||
# Create a StyleValue for the animation property
|
|
||||||
import hashlib
|
|
||||||
h = hashlib.sha256(kf_rule.encode()).hexdigest()[:6]
|
|
||||||
sv = StyleValue(
|
|
||||||
class_name=f"sx-{h}",
|
|
||||||
declarations=f"animation-name:{kf_name}",
|
|
||||||
keyframes=((kf_name, kf_rule),),
|
|
||||||
)
|
|
||||||
register_generated_rule(sv)
|
|
||||||
env[kf_name] = sv
|
|
||||||
return sv
|
|
||||||
|
|
||||||
|
|
||||||
def _sf_defcomp(expr: list, env: dict) -> Component:
|
def _sf_defcomp(expr: list, env: dict) -> Component:
|
||||||
"""``(defcomp ~name (&key ...) [:affinity :client|:server] body)``"""
|
"""``(defcomp ~name (&key ...) [:affinity :client|:server] body)``"""
|
||||||
if len(expr) < 4:
|
if len(expr) < 4:
|
||||||
@@ -1077,7 +1020,6 @@ _SPECIAL_FORMS: dict[str, Any] = {
|
|||||||
"fn": _sf_lambda,
|
"fn": _sf_lambda,
|
||||||
"define": _sf_define,
|
"define": _sf_define,
|
||||||
"defstyle": _sf_defstyle,
|
"defstyle": _sf_defstyle,
|
||||||
"defkeyframes": _sf_defkeyframes,
|
|
||||||
"defcomp": _sf_defcomp,
|
"defcomp": _sf_defcomp,
|
||||||
"defrelation": _sf_defrelation,
|
"defrelation": _sf_defrelation,
|
||||||
"begin": _sf_begin,
|
"begin": _sf_begin,
|
||||||
|
|||||||
@@ -645,7 +645,6 @@ details.group{{overflow:hidden}}details.group>summary{{list-style:none}}details.
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-stone-50 text-stone-900">
|
<body class="bg-stone-50 text-stone-900">
|
||||||
<script type="text/sx-styles" data-hash="{styles_hash}">{styles_json}</script>
|
|
||||||
<script type="text/sx" data-components data-hash="{component_hash}">{component_defs}</script>
|
<script type="text/sx" data-components data-hash="{component_hash}">{component_defs}</script>
|
||||||
<script type="text/sx-pages">{pages_sx}</script>
|
<script type="text/sx-pages">{pages_sx}</script>
|
||||||
<script type="text/sx" data-mount="body">{page_sx}</script>
|
<script type="text/sx" data-mount="body">{page_sx}</script>
|
||||||
@@ -830,14 +829,6 @@ def sx_page(ctx: dict, page_sx: str, *,
|
|||||||
import logging
|
import logging
|
||||||
logging.getLogger("sx").warning("Pretty-print page_sx failed: %s", e)
|
logging.getLogger("sx").warning("Pretty-print page_sx failed: %s", e)
|
||||||
|
|
||||||
# Style dictionary for client-side css primitive
|
|
||||||
styles_hash = _get_style_dict_hash()
|
|
||||||
client_styles_hash = _get_sx_styles_cookie()
|
|
||||||
if not _is_dev_mode() and client_styles_hash and client_styles_hash == styles_hash:
|
|
||||||
styles_json = "" # Client has cached version
|
|
||||||
else:
|
|
||||||
styles_json = _build_style_dict_json()
|
|
||||||
|
|
||||||
# Page registry for client-side routing
|
# Page registry for client-side routing
|
||||||
import logging
|
import logging
|
||||||
_plog = logging.getLogger("sx.pages")
|
_plog = logging.getLogger("sx.pages")
|
||||||
@@ -854,8 +845,6 @@ def sx_page(ctx: dict, page_sx: str, *,
|
|||||||
csrf=_html_escape(csrf),
|
csrf=_html_escape(csrf),
|
||||||
component_hash=component_hash,
|
component_hash=component_hash,
|
||||||
component_defs=component_defs,
|
component_defs=component_defs,
|
||||||
styles_hash=styles_hash,
|
|
||||||
styles_json=styles_json,
|
|
||||||
pages_sx=pages_sx,
|
pages_sx=pages_sx,
|
||||||
page_sx=page_sx,
|
page_sx=page_sx,
|
||||||
sx_css=sx_css,
|
sx_css=sx_css,
|
||||||
@@ -917,10 +906,6 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
|
|||||||
title = ctx.get("base_title", "Rose Ash")
|
title = ctx.get("base_title", "Rose Ash")
|
||||||
csrf = _get_csrf_token()
|
csrf = _get_csrf_token()
|
||||||
|
|
||||||
styles_hash = _get_style_dict_hash()
|
|
||||||
client_styles_hash = _get_sx_styles_cookie()
|
|
||||||
styles_json = "" if (not _is_dev_mode() and client_styles_hash == styles_hash) else _build_style_dict_json()
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
pages_sx = _build_pages_sx(current_app.name)
|
pages_sx = _build_pages_sx(current_app.name)
|
||||||
@@ -963,7 +948,6 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
|
|||||||
'</style>\n'
|
'</style>\n'
|
||||||
'</head>\n'
|
'</head>\n'
|
||||||
'<body class="bg-stone-50 text-stone-900">\n'
|
'<body class="bg-stone-50 text-stone-900">\n'
|
||||||
f'<script type="text/sx-styles" data-hash="{styles_hash}">{styles_json}</script>\n'
|
|
||||||
f'<script type="text/sx" data-components data-hash="{component_hash}">{component_defs}</script>\n'
|
f'<script type="text/sx" data-components data-hash="{component_hash}">{component_defs}</script>\n'
|
||||||
f'<script type="text/sx-pages">{pages_sx}</script>\n'
|
f'<script type="text/sx-pages">{pages_sx}</script>\n'
|
||||||
# Server-rendered HTML — suspense placeholders are real DOM elements
|
# Server-rendered HTML — suspense placeholders are real DOM elements
|
||||||
@@ -999,58 +983,6 @@ def sx_streaming_resolve_script(suspension_id: str, sx_source: str,
|
|||||||
|
|
||||||
|
|
||||||
_SCRIPT_HASH_CACHE: dict[str, str] = {}
|
_SCRIPT_HASH_CACHE: dict[str, str] = {}
|
||||||
_STYLE_DICT_JSON: str = ""
|
|
||||||
_STYLE_DICT_HASH: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
def _build_style_dict_json() -> str:
|
|
||||||
"""Build compact JSON style dictionary for client-side css primitive."""
|
|
||||||
global _STYLE_DICT_JSON, _STYLE_DICT_HASH
|
|
||||||
if _STYLE_DICT_JSON:
|
|
||||||
return _STYLE_DICT_JSON
|
|
||||||
|
|
||||||
import json
|
|
||||||
from .style_dict import (
|
|
||||||
STYLE_ATOMS, PSEUDO_VARIANTS, RESPONSIVE_BREAKPOINTS,
|
|
||||||
KEYFRAMES, ARBITRARY_PATTERNS, CHILD_SELECTOR_ATOMS,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Derive child selector prefixes from CHILD_SELECTOR_ATOMS
|
|
||||||
prefixes = set()
|
|
||||||
for atom in CHILD_SELECTOR_ATOMS:
|
|
||||||
# "space-y-4" → "space-y-", "divide-y" → "divide-"
|
|
||||||
for sep in ("space-x-", "space-y-", "divide-x", "divide-y"):
|
|
||||||
if atom.startswith(sep):
|
|
||||||
prefixes.add(sep)
|
|
||||||
break
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"a": STYLE_ATOMS,
|
|
||||||
"v": PSEUDO_VARIANTS,
|
|
||||||
"b": RESPONSIVE_BREAKPOINTS,
|
|
||||||
"k": KEYFRAMES,
|
|
||||||
"p": ARBITRARY_PATTERNS,
|
|
||||||
"c": sorted(prefixes),
|
|
||||||
}
|
|
||||||
_STYLE_DICT_JSON = json.dumps(data, separators=(",", ":"))
|
|
||||||
_STYLE_DICT_HASH = hashlib.md5(_STYLE_DICT_JSON.encode()).hexdigest()[:8]
|
|
||||||
return _STYLE_DICT_JSON
|
|
||||||
|
|
||||||
|
|
||||||
def _get_style_dict_hash() -> str:
|
|
||||||
"""Get the hash of the style dictionary JSON."""
|
|
||||||
if not _STYLE_DICT_HASH:
|
|
||||||
_build_style_dict_json()
|
|
||||||
return _STYLE_DICT_HASH
|
|
||||||
|
|
||||||
|
|
||||||
def _get_sx_styles_cookie() -> str:
|
|
||||||
"""Read the sx-styles-hash cookie from the current request."""
|
|
||||||
try:
|
|
||||||
from quart import request
|
|
||||||
return request.cookies.get("sx-styles-hash", "")
|
|
||||||
except RuntimeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _script_hash(filename: str) -> str:
|
def _script_hash(filename: str) -> str:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from __future__ import annotations
|
|||||||
import contextvars
|
import contextvars
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .types import Component, Keyword, Lambda, Macro, NIL, StyleValue, Symbol
|
from .types import Component, Keyword, Lambda, Macro, NIL, Symbol
|
||||||
from .evaluator import _eval as _raw_eval, _call_component as _raw_call_component, _expand_macro, _trampoline
|
from .evaluator import _eval as _raw_eval, _call_component as _raw_call_component, _expand_macro, _trampoline
|
||||||
|
|
||||||
def _eval(expr, env):
|
def _eval(expr, env):
|
||||||
@@ -510,19 +510,6 @@ def _render_element(tag: str, args: list, env: dict[str, Any]) -> str:
|
|||||||
children.append(arg)
|
children.append(arg)
|
||||||
i += 1
|
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
|
# Collect CSS classes if collector is active
|
||||||
class_val = attrs.get("class")
|
class_val = attrs.get("class")
|
||||||
if class_val is not None and class_val is not NIL and class_val is not False:
|
if class_val is not None and class_val is not NIL and class_val is not False:
|
||||||
|
|||||||
@@ -382,11 +382,6 @@ def serialize(expr: Any, indent: int = 0, pretty: bool = False) -> str:
|
|||||||
items.append(serialize(v, indent, pretty))
|
items.append(serialize(v, indent, pretty))
|
||||||
return "{" + " ".join(items) + "}"
|
return "{" + " ".join(items) + "}"
|
||||||
|
|
||||||
# StyleValue — serialize as class name string
|
|
||||||
from .types import StyleValue
|
|
||||||
if isinstance(expr, StyleValue):
|
|
||||||
return f'"{expr.class_name}"'
|
|
||||||
|
|
||||||
# _RawHTML — pre-rendered HTML; wrap as (raw! "...") for SX wire format
|
# _RawHTML — pre-rendered HTML; wrap as (raw! "...") for SX wire format
|
||||||
from .html import _RawHTML
|
from .html import _RawHTML
|
||||||
if isinstance(expr, _RawHTML):
|
if isinstance(expr, _RawHTML):
|
||||||
|
|||||||
@@ -89,37 +89,6 @@ def prim_strip_tags(s: str) -> str:
|
|||||||
return re.sub(r"<[^>]+>", "", s)
|
return re.sub(r"<[^>]+>", "", s)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# stdlib.style
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@register_primitive("css")
|
|
||||||
def prim_css(*args: Any) -> Any:
|
|
||||||
"""``(css :flex :gap-4 :hover:bg-sky-200)`` → StyleValue."""
|
|
||||||
from .types import Keyword
|
|
||||||
from .style_resolver import resolve_style
|
|
||||||
atoms = tuple(
|
|
||||||
(a.name if isinstance(a, Keyword) else str(a))
|
|
||||||
for a in args if a is not None and a is not NIL and a is not False
|
|
||||||
)
|
|
||||||
if not atoms:
|
|
||||||
return NIL
|
|
||||||
return resolve_style(atoms)
|
|
||||||
|
|
||||||
|
|
||||||
@register_primitive("merge-styles")
|
|
||||||
def prim_merge_styles(*styles: Any) -> Any:
|
|
||||||
"""``(merge-styles style1 style2)`` → merged StyleValue."""
|
|
||||||
from .types import StyleValue
|
|
||||||
from .style_resolver import merge_styles
|
|
||||||
valid = [s for s in styles if isinstance(s, StyleValue)]
|
|
||||||
if not valid:
|
|
||||||
return NIL
|
|
||||||
if len(valid) == 1:
|
|
||||||
return valid[0]
|
|
||||||
return merge_styles(valid)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# stdlib.debug
|
# stdlib.debug
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ Only these types may cross the host-SX boundary:
|
|||||||
| list | `list` | `Array` | `Vec<SxValue>` |
|
| list | `list` | `Array` | `Vec<SxValue>` |
|
||||||
| dict | `dict` | `Object` / `Map` | `HashMap<String, SxValue>` |
|
| dict | `dict` | `Object` / `Map` | `HashMap<String, SxValue>` |
|
||||||
| sx-source | `SxExpr` wrapper | `string` | `String` |
|
| sx-source | `SxExpr` wrapper | `string` | `String` |
|
||||||
| style-value | `StyleValue` | `StyleValue` | `StyleValue` |
|
|
||||||
|
|
||||||
**NOT allowed:** ORM models, datetime objects, request objects, raw callables, framework types. Convert at the edge before crossing.
|
**NOT allowed:** ORM models, datetime objects, request objects, raw callables, framework types. Convert at the edge before crossing.
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,6 @@
|
|||||||
(create-fragment)
|
(create-fragment)
|
||||||
(render-dom-list expr env ns))
|
(render-dom-list expr env ns))
|
||||||
|
|
||||||
;; Style value → text of class name
|
|
||||||
"style-value" (create-text-node (style-value-class expr))
|
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else (create-text-node (str expr)))))
|
:else (create-text-node (str expr)))))
|
||||||
|
|
||||||
@@ -147,8 +144,7 @@
|
|||||||
(let ((new-ns (cond (= tag "svg") SVG_NS
|
(let ((new-ns (cond (= tag "svg") SVG_NS
|
||||||
(= tag "math") MATH_NS
|
(= tag "math") MATH_NS
|
||||||
:else ns))
|
:else ns))
|
||||||
(el (dom-create-element tag new-ns))
|
(el (dom-create-element tag new-ns)))
|
||||||
(extra-class nil))
|
|
||||||
|
|
||||||
;; Process args: keywords → attrs, others → children
|
;; Process args: keywords → attrs, others → children
|
||||||
(reduce
|
(reduce
|
||||||
@@ -168,9 +164,6 @@
|
|||||||
;; nil or false → skip
|
;; nil or false → skip
|
||||||
(or (nil? attr-val) (= attr-val false))
|
(or (nil? attr-val) (= attr-val false))
|
||||||
nil
|
nil
|
||||||
;; :style StyleValue → convert to class
|
|
||||||
(and (= attr-name "style") (style-value? attr-val))
|
|
||||||
(set! extra-class (style-value-class attr-val))
|
|
||||||
;; Boolean attr
|
;; Boolean attr
|
||||||
(contains? BOOLEAN_ATTRS attr-name)
|
(contains? BOOLEAN_ATTRS attr-name)
|
||||||
(when attr-val (dom-set-attr el attr-name ""))
|
(when attr-val (dom-set-attr el attr-name ""))
|
||||||
@@ -190,12 +183,6 @@
|
|||||||
(dict "i" 0 "skip" false)
|
(dict "i" 0 "skip" false)
|
||||||
args)
|
args)
|
||||||
|
|
||||||
;; Merge StyleValue class
|
|
||||||
(when extra-class
|
|
||||||
(let ((existing (dom-get-attr el "class")))
|
|
||||||
(dom-set-attr el "class"
|
|
||||||
(if existing (str existing " " extra-class) extra-class))))
|
|
||||||
|
|
||||||
el)))
|
el)))
|
||||||
|
|
||||||
|
|
||||||
@@ -297,7 +284,7 @@
|
|||||||
|
|
||||||
(define RENDER_DOM_FORMS
|
(define RENDER_DOM_FORMS
|
||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defmacro" "defstyle" "defkeyframes" "defhandler"
|
"define" "defcomp" "defmacro" "defstyle" "defhandler"
|
||||||
"map" "map-indexed" "filter" "for-each"))
|
"map" "map-indexed" "filter" "for-each"))
|
||||||
|
|
||||||
(define render-dom-form?
|
(define render-dom-form?
|
||||||
@@ -450,7 +437,6 @@
|
|||||||
;;
|
;;
|
||||||
;; From render.sx:
|
;; From render.sx:
|
||||||
;; HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, definition-form?
|
;; HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, definition-form?
|
||||||
;; style-value?, style-value-class
|
|
||||||
;;
|
;;
|
||||||
;; From eval.sx:
|
;; From eval.sx:
|
||||||
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond
|
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"boolean" (if val "true" "false")
|
"boolean" (if val "true" "false")
|
||||||
"list" (render-list-to-html val env)
|
"list" (render-list-to-html val env)
|
||||||
"raw-html" (raw-html-content val)
|
"raw-html" (raw-html-content val)
|
||||||
"style-value" (style-value-class val)
|
|
||||||
:else (escape-html (str val)))))
|
:else (escape-html (str val)))))
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@
|
|||||||
|
|
||||||
(define RENDER_HTML_FORMS
|
(define RENDER_HTML_FORMS
|
||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defmacro" "defstyle" "defkeyframes" "defhandler"
|
"define" "defcomp" "defmacro" "defstyle" "defhandler"
|
||||||
"map" "map-indexed" "filter" "for-each"))
|
"map" "map-indexed" "filter" "for-each"))
|
||||||
|
|
||||||
(define render-html-form?
|
(define render-html-form?
|
||||||
@@ -293,7 +292,7 @@
|
|||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;;
|
;;
|
||||||
;; Inherited from render.sx:
|
;; Inherited from render.sx:
|
||||||
;; escape-html, escape-attr, raw-html-content, style-value?, style-value-class
|
;; escape-html, escape-attr, raw-html-content
|
||||||
;;
|
;;
|
||||||
;; From eval.sx:
|
;; From eval.sx:
|
||||||
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond
|
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import contextvars
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ..types import Component, Keyword, Lambda, Macro, NIL, StyleValue, Symbol
|
from ..types import Component, Keyword, Lambda, Macro, NIL, Symbol
|
||||||
from ..parser import SxExpr, serialize
|
from ..parser import SxExpr, serialize
|
||||||
from ..primitives_io import IO_PRIMITIVES, RequestContext, execute_io
|
from ..primitives_io import IO_PRIMITIVES, RequestContext, execute_io
|
||||||
from ..html import (
|
from ..html import (
|
||||||
@@ -250,18 +250,6 @@ async def _arender_element(tag, args, env, ctx):
|
|||||||
children.append(arg)
|
children.append(arg)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# StyleValue → class
|
|
||||||
style_val = attrs.get("style")
|
|
||||||
if isinstance(style_val, StyleValue):
|
|
||||||
from ..css_registry import register_generated_rule
|
|
||||||
register_generated_rule(style_val)
|
|
||||||
existing = attrs.get("class")
|
|
||||||
if existing and existing is not NIL and existing is not False:
|
|
||||||
attrs["class"] = f"{existing} {style_val.class_name}"
|
|
||||||
else:
|
|
||||||
attrs["class"] = style_val.class_name
|
|
||||||
del attrs["style"]
|
|
||||||
|
|
||||||
class_val = attrs.get("class")
|
class_val = attrs.get("class")
|
||||||
if class_val is not None and class_val is not NIL and class_val is not False:
|
if class_val is not None and class_val is not NIL and class_val is not False:
|
||||||
collector = css_class_collector.get(None)
|
collector = css_class_collector.get(None)
|
||||||
@@ -464,7 +452,6 @@ _ASYNC_RENDER_FORMS = {
|
|||||||
"do": _arsf_begin,
|
"do": _arsf_begin,
|
||||||
"define": _arsf_define,
|
"define": _arsf_define,
|
||||||
"defstyle": _arsf_define,
|
"defstyle": _arsf_define,
|
||||||
"defkeyframes": _arsf_define,
|
|
||||||
"defcomp": _arsf_define,
|
"defcomp": _arsf_define,
|
||||||
"defmacro": _arsf_define,
|
"defmacro": _arsf_define,
|
||||||
"defhandler": _arsf_define,
|
"defhandler": _arsf_define,
|
||||||
@@ -716,23 +703,18 @@ async def _aser_call(name, args, env, ctx):
|
|||||||
if isinstance(arg, Keyword) and i + 1 < len(args):
|
if isinstance(arg, Keyword) and i + 1 < len(args):
|
||||||
val = await _aser(args[i + 1], env, ctx)
|
val = await _aser(args[i + 1], env, ctx)
|
||||||
if val is not NIL and val is not None:
|
if val is not NIL and val is not None:
|
||||||
if arg.name == "style" and isinstance(val, StyleValue):
|
parts.append(f":{arg.name}")
|
||||||
from ..css_registry import register_generated_rule
|
if isinstance(val, list):
|
||||||
register_generated_rule(val)
|
live = [v for v in val if v is not NIL and v is not None]
|
||||||
extra_class = val.class_name
|
items = [serialize(v) for v in live]
|
||||||
else:
|
if not items:
|
||||||
parts.append(f":{arg.name}")
|
parts.append("nil")
|
||||||
if isinstance(val, list):
|
elif any(isinstance(v, SxExpr) for v in live):
|
||||||
live = [v for v in val if v is not NIL and v is not None]
|
parts.append("(<> " + " ".join(items) + ")")
|
||||||
items = [serialize(v) for v in live]
|
|
||||||
if not items:
|
|
||||||
parts.append("nil")
|
|
||||||
elif any(isinstance(v, SxExpr) for v in live):
|
|
||||||
parts.append("(<> " + " ".join(items) + ")")
|
|
||||||
else:
|
|
||||||
parts.append("(list " + " ".join(items) + ")")
|
|
||||||
else:
|
else:
|
||||||
parts.append(serialize(val))
|
parts.append("(list " + " ".join(items) + ")")
|
||||||
|
else:
|
||||||
|
parts.append(serialize(val))
|
||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
result = await _aser(arg, env, ctx)
|
result = await _aser(arg, env, ctx)
|
||||||
@@ -996,7 +978,6 @@ _ASER_FORMS = {
|
|||||||
"fn": _assf_lambda,
|
"fn": _assf_lambda,
|
||||||
"define": _assf_define,
|
"define": _assf_define,
|
||||||
"defstyle": _assf_define,
|
"defstyle": _assf_define,
|
||||||
"defkeyframes": _assf_define,
|
|
||||||
"defcomp": _assf_define,
|
"defcomp": _assf_define,
|
||||||
"defmacro": _assf_define,
|
"defmacro": _assf_define,
|
||||||
"defhandler": _assf_define,
|
"defhandler": _assf_define,
|
||||||
|
|||||||
@@ -3,16 +3,14 @@
|
|||||||
;;
|
;;
|
||||||
;; Handles the browser startup lifecycle:
|
;; Handles the browser startup lifecycle:
|
||||||
;; 1. CSS tracking init
|
;; 1. CSS tracking init
|
||||||
;; 2. Style dictionary loading (from <script type="text/sx-styles">)
|
;; 2. Component script processing (from <script type="text/sx">)
|
||||||
;; 3. Component script processing (from <script type="text/sx">)
|
;; 3. Hydration of [data-sx] elements
|
||||||
;; 4. Hydration of [data-sx] elements
|
;; 4. Engine element processing
|
||||||
;; 5. Engine element processing
|
|
||||||
;;
|
;;
|
||||||
;; Also provides the public mounting/hydration API:
|
;; Also provides the public mounting/hydration API:
|
||||||
;; mount, hydrate, update, render-component
|
;; mount, hydrate, update, render-component
|
||||||
;;
|
;;
|
||||||
;; Depends on:
|
;; Depends on:
|
||||||
;; cssx.sx — load-style-dict
|
|
||||||
;; orchestration.sx — process-elements, engine-init
|
;; orchestration.sx — process-elements, engine-init
|
||||||
;; adapter-dom.sx — render-to-dom
|
;; adapter-dom.sx — render-to-dom
|
||||||
;; render.sx — shared registries
|
;; render.sx — shared registries
|
||||||
@@ -275,58 +273,6 @@
|
|||||||
(set-sx-comp-cookie hash))))))
|
(set-sx-comp-cookie hash))))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Style dictionary initialization
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define init-style-dict
|
|
||||||
(fn ()
|
|
||||||
;; Process <script type="text/sx-styles"> tags with caching.
|
|
||||||
(let ((scripts (query-style-scripts)))
|
|
||||||
(for-each
|
|
||||||
(fn (s)
|
|
||||||
(when (not (is-processed? s "styles"))
|
|
||||||
(mark-processed! s "styles")
|
|
||||||
(let ((text (dom-text-content s))
|
|
||||||
(hash (dom-get-attr s "data-hash")))
|
|
||||||
(if (nil? hash)
|
|
||||||
;; No hash — just parse inline
|
|
||||||
(when (and text (not (empty? (trim text))))
|
|
||||||
(parse-and-load-style-dict text))
|
|
||||||
;; Hash-based caching
|
|
||||||
(let ((has-inline (and text (not (empty? (trim text))))))
|
|
||||||
(let ((cached-hash (local-storage-get "sx-styles-hash")))
|
|
||||||
(if (= cached-hash hash)
|
|
||||||
;; Cache hit
|
|
||||||
(if has-inline
|
|
||||||
(do
|
|
||||||
(local-storage-set "sx-styles-src" text)
|
|
||||||
(parse-and-load-style-dict text)
|
|
||||||
(log-info "styles: downloaded (cookie stale)"))
|
|
||||||
(let ((cached (local-storage-get "sx-styles-src")))
|
|
||||||
(if cached
|
|
||||||
(do
|
|
||||||
(parse-and-load-style-dict cached)
|
|
||||||
(log-info (str "styles: cached (" hash ")")))
|
|
||||||
(do
|
|
||||||
(clear-sx-styles-cookie)
|
|
||||||
(browser-reload)))))
|
|
||||||
;; Cache miss
|
|
||||||
(if has-inline
|
|
||||||
(do
|
|
||||||
(local-storage-set "sx-styles-hash" hash)
|
|
||||||
(local-storage-set "sx-styles-src" text)
|
|
||||||
(parse-and-load-style-dict text)
|
|
||||||
(log-info (str "styles: downloaded (" hash ")")))
|
|
||||||
(do
|
|
||||||
(local-storage-remove "sx-styles-hash")
|
|
||||||
(local-storage-remove "sx-styles-src")
|
|
||||||
(clear-sx-styles-cookie)
|
|
||||||
(browser-reload)))))
|
|
||||||
(set-sx-styles-cookie hash))))))
|
|
||||||
scripts))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; Page registry for client-side routing
|
;; Page registry for client-side routing
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -375,7 +321,6 @@
|
|||||||
(do
|
(do
|
||||||
(log-info (str "sx-browser " SX_VERSION))
|
(log-info (str "sx-browser " SX_VERSION))
|
||||||
(init-css-tracking)
|
(init-css-tracking)
|
||||||
(init-style-dict)
|
|
||||||
(process-page-scripts)
|
(process-page-scripts)
|
||||||
(process-sx-scripts nil)
|
(process-sx-scripts nil)
|
||||||
(sx-hydrate-elements nil)
|
(sx-hydrate-elements nil)
|
||||||
@@ -389,9 +334,6 @@
|
|||||||
;; From orchestration.sx:
|
;; From orchestration.sx:
|
||||||
;; process-elements, init-css-tracking
|
;; process-elements, init-css-tracking
|
||||||
;;
|
;;
|
||||||
;; From cssx.sx:
|
|
||||||
;; load-style-dict
|
|
||||||
;;
|
|
||||||
;; === DOM / Render ===
|
;; === DOM / Render ===
|
||||||
;; (resolve-mount-target target) → Element (string → querySelector, else identity)
|
;; (resolve-mount-target target) → Element (string → querySelector, else identity)
|
||||||
;; (sx-render-with-env source extra-env) → DOM node (parse + render with componentEnv + extra)
|
;; (sx-render-with-env source extra-env) → DOM node (parse + render with componentEnv + extra)
|
||||||
@@ -420,7 +362,6 @@
|
|||||||
;;
|
;;
|
||||||
;; === Script queries ===
|
;; === Script queries ===
|
||||||
;; (query-sx-scripts root) → list of <script type="text/sx"> elements
|
;; (query-sx-scripts root) → list of <script type="text/sx"> elements
|
||||||
;; (query-style-scripts) → list of <script type="text/sx-styles"> elements
|
|
||||||
;; (query-page-scripts) → list of <script type="text/sx-pages"> elements
|
;; (query-page-scripts) → list of <script type="text/sx-pages"> elements
|
||||||
;;
|
;;
|
||||||
;; === localStorage ===
|
;; === localStorage ===
|
||||||
@@ -431,8 +372,6 @@
|
|||||||
;; === Cookies ===
|
;; === Cookies ===
|
||||||
;; (set-sx-comp-cookie hash) → void
|
;; (set-sx-comp-cookie hash) → void
|
||||||
;; (clear-sx-comp-cookie) → void
|
;; (clear-sx-comp-cookie) → void
|
||||||
;; (set-sx-styles-cookie hash) → void
|
|
||||||
;; (clear-sx-styles-cookie) → void
|
|
||||||
;;
|
;;
|
||||||
;; === Env ===
|
;; === Env ===
|
||||||
;; (parse-env-attr el) → dict (parse data-sx-env JSON attr)
|
;; (parse-env-attr el) → dict (parse data-sx-env JSON attr)
|
||||||
@@ -444,8 +383,6 @@
|
|||||||
;; (log-parse-error label text err) → void (diagnostic parse error)
|
;; (log-parse-error label text err) → void (diagnostic parse error)
|
||||||
;;
|
;;
|
||||||
;; === JSON parsing ===
|
;; === JSON parsing ===
|
||||||
;; (parse-and-load-style-dict text) → void (JSON.parse + load-style-dict)
|
|
||||||
;;
|
|
||||||
;; === Processing markers ===
|
;; === Processing markers ===
|
||||||
;; (mark-processed! el key) → void
|
;; (mark-processed! el key) → void
|
||||||
;; (is-processed? el key) → boolean
|
;; (is-processed? el key) → boolean
|
||||||
|
|||||||
@@ -207,10 +207,6 @@ class JSEmitter:
|
|||||||
"ho-every": "hoEvery",
|
"ho-every": "hoEvery",
|
||||||
"ho-for-each": "hoForEach",
|
"ho-for-each": "hoForEach",
|
||||||
"sf-defstyle": "sfDefstyle",
|
"sf-defstyle": "sfDefstyle",
|
||||||
"sf-defkeyframes": "sfDefkeyframes",
|
|
||||||
"build-keyframes": "buildKeyframes",
|
|
||||||
"style-value?": "isStyleValue",
|
|
||||||
"style-value-class": "styleValueClass",
|
|
||||||
"kf-name": "kfName",
|
"kf-name": "kfName",
|
||||||
"special-form?": "isSpecialForm",
|
"special-form?": "isSpecialForm",
|
||||||
"ho-form?": "isHoForm",
|
"ho-form?": "isHoForm",
|
||||||
@@ -460,32 +456,6 @@ class JSEmitter:
|
|||||||
"format-date": "formatDate",
|
"format-date": "formatDate",
|
||||||
"format-decimal": "formatDecimal",
|
"format-decimal": "formatDecimal",
|
||||||
"parse-int": "parseInt_",
|
"parse-int": "parseInt_",
|
||||||
# cssx.sx
|
|
||||||
"_style-atoms": "_styleAtoms",
|
|
||||||
"_pseudo-variants": "_pseudoVariants",
|
|
||||||
"_responsive-breakpoints": "_responsiveBreakpoints",
|
|
||||||
"_style-keyframes": "_styleKeyframes",
|
|
||||||
"_arbitrary-patterns": "_arbitraryPatterns",
|
|
||||||
"_child-selector-prefixes": "_childSelectorPrefixes",
|
|
||||||
"_style-cache": "_styleCache",
|
|
||||||
"_injected-styles": "_injectedStyles",
|
|
||||||
"load-style-dict": "loadStyleDict",
|
|
||||||
"split-variant": "splitVariant",
|
|
||||||
"resolve-atom": "resolveAtom",
|
|
||||||
"is-child-selector-atom?": "isChildSelectorAtom",
|
|
||||||
"hash-style": "hashStyle",
|
|
||||||
"resolve-style": "resolveStyle",
|
|
||||||
"merge-style-values": "mergeStyleValues",
|
|
||||||
"fnv1a-hash": "fnv1aHash",
|
|
||||||
"compile-regex": "compileRegex",
|
|
||||||
"regex-match": "regexMatch",
|
|
||||||
"regex-replace-groups": "regexReplaceGroups",
|
|
||||||
"make-style-value": "makeStyleValue_",
|
|
||||||
"style-value-declarations": "styleValueDeclarations",
|
|
||||||
"style-value-media-rules": "styleValueMediaRules",
|
|
||||||
"style-value-pseudo-rules": "styleValuePseudoRules",
|
|
||||||
"style-value-keyframes": "styleValueKeyframes_",
|
|
||||||
"inject-style-value": "injectStyleValue",
|
|
||||||
# boot.sx
|
# boot.sx
|
||||||
"HEAD_HOIST_SELECTOR": "HEAD_HOIST_SELECTOR",
|
"HEAD_HOIST_SELECTOR": "HEAD_HOIST_SELECTOR",
|
||||||
"hoist-head-elements-full": "hoistHeadElementsFull",
|
"hoist-head-elements-full": "hoistHeadElementsFull",
|
||||||
@@ -495,7 +465,6 @@ class JSEmitter:
|
|||||||
"sx-render-component": "sxRenderComponent",
|
"sx-render-component": "sxRenderComponent",
|
||||||
"process-sx-scripts": "processSxScripts",
|
"process-sx-scripts": "processSxScripts",
|
||||||
"process-component-script": "processComponentScript",
|
"process-component-script": "processComponentScript",
|
||||||
"init-style-dict": "initStyleDict",
|
|
||||||
"SX_VERSION": "SX_VERSION",
|
"SX_VERSION": "SX_VERSION",
|
||||||
"boot-init": "bootInit",
|
"boot-init": "bootInit",
|
||||||
"resolve-suspense": "resolveSuspense",
|
"resolve-suspense": "resolveSuspense",
|
||||||
@@ -507,21 +476,17 @@ class JSEmitter:
|
|||||||
"set-document-title": "setDocumentTitle",
|
"set-document-title": "setDocumentTitle",
|
||||||
"remove-head-element": "removeHeadElement",
|
"remove-head-element": "removeHeadElement",
|
||||||
"query-sx-scripts": "querySxScripts",
|
"query-sx-scripts": "querySxScripts",
|
||||||
"query-style-scripts": "queryStyleScripts",
|
|
||||||
"local-storage-get": "localStorageGet",
|
"local-storage-get": "localStorageGet",
|
||||||
"local-storage-set": "localStorageSet",
|
"local-storage-set": "localStorageSet",
|
||||||
"local-storage-remove": "localStorageRemove",
|
"local-storage-remove": "localStorageRemove",
|
||||||
"set-sx-comp-cookie": "setSxCompCookie",
|
"set-sx-comp-cookie": "setSxCompCookie",
|
||||||
"clear-sx-comp-cookie": "clearSxCompCookie",
|
"clear-sx-comp-cookie": "clearSxCompCookie",
|
||||||
"set-sx-styles-cookie": "setSxStylesCookie",
|
|
||||||
"clear-sx-styles-cookie": "clearSxStylesCookie",
|
|
||||||
"parse-env-attr": "parseEnvAttr",
|
"parse-env-attr": "parseEnvAttr",
|
||||||
"store-env-attr": "storeEnvAttr",
|
"store-env-attr": "storeEnvAttr",
|
||||||
"to-kebab": "toKebab",
|
"to-kebab": "toKebab",
|
||||||
"log-info": "logInfo",
|
"log-info": "logInfo",
|
||||||
"log-warn": "logWarn",
|
"log-warn": "logWarn",
|
||||||
"log-parse-error": "logParseError",
|
"log-parse-error": "logParseError",
|
||||||
"parse-and-load-style-dict": "parseAndLoadStyleDict",
|
|
||||||
"_page-routes": "_pageRoutes",
|
"_page-routes": "_pageRoutes",
|
||||||
"process-page-scripts": "processPageScripts",
|
"process-page-scripts": "processPageScripts",
|
||||||
"query-page-scripts": "queryPageScripts",
|
"query-page-scripts": "queryPageScripts",
|
||||||
@@ -1066,7 +1031,6 @@ ADAPTER_FILES = {
|
|||||||
"dom": ("adapter-dom.sx", "adapter-dom"),
|
"dom": ("adapter-dom.sx", "adapter-dom"),
|
||||||
"engine": ("engine.sx", "engine"),
|
"engine": ("engine.sx", "engine"),
|
||||||
"orchestration": ("orchestration.sx","orchestration"),
|
"orchestration": ("orchestration.sx","orchestration"),
|
||||||
"cssx": ("cssx.sx", "cssx"),
|
|
||||||
"boot": ("boot.sx", "boot"),
|
"boot": ("boot.sx", "boot"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,8 +1038,7 @@ ADAPTER_FILES = {
|
|||||||
ADAPTER_DEPS = {
|
ADAPTER_DEPS = {
|
||||||
"engine": ["dom"],
|
"engine": ["dom"],
|
||||||
"orchestration": ["engine", "dom"],
|
"orchestration": ["engine", "dom"],
|
||||||
"cssx": [],
|
"boot": ["dom", "engine", "orchestration", "parser"],
|
||||||
"boot": ["dom", "engine", "orchestration", "cssx", "parser"],
|
|
||||||
"parser": [],
|
"parser": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1314,7 +1277,7 @@ ASYNC_IO_JS = '''
|
|||||||
|
|
||||||
// define/defcomp/defmacro — eval for side effects
|
// define/defcomp/defmacro — eval for side effects
|
||||||
if (hname === "define" || hname === "defcomp" || hname === "defmacro" ||
|
if (hname === "define" || hname === "defcomp" || hname === "defmacro" ||
|
||||||
hname === "defstyle" || hname === "defkeyframes" || hname === "defhandler") {
|
hname === "defstyle" || hname === "defhandler") {
|
||||||
trampoline(evalExpr(expr, env));
|
trampoline(evalExpr(expr, env));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1436,11 +1399,7 @@ ASYNC_IO_JS = '''
|
|||||||
})(attrName, attrVal);
|
})(attrName, attrVal);
|
||||||
} else {
|
} else {
|
||||||
if (!isNil(attrVal) && attrVal !== false) {
|
if (!isNil(attrVal) && attrVal !== false) {
|
||||||
if (attrName === "class" && attrVal && attrVal._styleValue) {
|
if (contains(BOOLEAN_ATTRS, attrName)) {
|
||||||
el.setAttribute("class", (el.getAttribute("class") || "") + " " + attrVal.className);
|
|
||||||
} else if (attrName === "style" && attrVal && attrVal._styleValue) {
|
|
||||||
el.setAttribute("class", (el.getAttribute("class") || "") + " " + attrVal.className);
|
|
||||||
} else if (contains(BOOLEAN_ATTRS, attrName)) {
|
|
||||||
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
||||||
} else if (attrVal === true) {
|
} else if (attrVal === true) {
|
||||||
el.setAttribute(attrName, "");
|
el.setAttribute(attrName, "");
|
||||||
@@ -1851,7 +1810,6 @@ def compile_ref_to_js(
|
|||||||
"dom": PLATFORM_DOM_JS,
|
"dom": PLATFORM_DOM_JS,
|
||||||
"engine": PLATFORM_ENGINE_PURE_JS,
|
"engine": PLATFORM_ENGINE_PURE_JS,
|
||||||
"orchestration": PLATFORM_ORCHESTRATION_JS,
|
"orchestration": PLATFORM_ORCHESTRATION_JS,
|
||||||
"cssx": PLATFORM_CSSX_JS,
|
|
||||||
"boot": PLATFORM_BOOT_JS,
|
"boot": PLATFORM_BOOT_JS,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1886,7 +1844,7 @@ def compile_ref_to_js(
|
|||||||
("eval.sx", "eval"),
|
("eval.sx", "eval"),
|
||||||
("render.sx", "render (core)"),
|
("render.sx", "render (core)"),
|
||||||
]
|
]
|
||||||
for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "cssx", "boot"):
|
for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "boot"):
|
||||||
if name in adapter_set:
|
if name in adapter_set:
|
||||||
sx_files.append(ADAPTER_FILES[name])
|
sx_files.append(ADAPTER_FILES[name])
|
||||||
for name in sorted(spec_mod_set):
|
for name in sorted(spec_mod_set):
|
||||||
@@ -1917,7 +1875,6 @@ def compile_ref_to_js(
|
|||||||
has_dom = "dom" in adapter_set
|
has_dom = "dom" in adapter_set
|
||||||
has_engine = "engine" in adapter_set
|
has_engine = "engine" in adapter_set
|
||||||
has_orch = "orchestration" in adapter_set
|
has_orch = "orchestration" in adapter_set
|
||||||
has_cssx = "cssx" in adapter_set
|
|
||||||
has_boot = "boot" in adapter_set
|
has_boot = "boot" in adapter_set
|
||||||
has_parser = "parser" in adapter_set
|
has_parser = "parser" in adapter_set
|
||||||
adapter_label = "+".join(sorted(adapter_set)) if adapter_set else "core-only"
|
adapter_label = "+".join(sorted(adapter_set)) if adapter_set else "core-only"
|
||||||
@@ -1959,7 +1916,7 @@ def compile_ref_to_js(
|
|||||||
# Platform JS for selected adapters
|
# Platform JS for selected adapters
|
||||||
if not has_dom:
|
if not has_dom:
|
||||||
parts.append("\n var _hasDom = false;\n")
|
parts.append("\n var _hasDom = false;\n")
|
||||||
for name in ("dom", "engine", "orchestration", "cssx", "boot"):
|
for name in ("dom", "engine", "orchestration", "boot"):
|
||||||
if name in adapter_set and name in adapter_platform:
|
if name in adapter_set and name in adapter_platform:
|
||||||
parts.append(adapter_platform[name])
|
parts.append(adapter_platform[name])
|
||||||
|
|
||||||
@@ -1968,7 +1925,7 @@ def compile_ref_to_js(
|
|||||||
parts.append(CONTINUATIONS_JS)
|
parts.append(CONTINUATIONS_JS)
|
||||||
if has_dom:
|
if has_dom:
|
||||||
parts.append(ASYNC_IO_JS)
|
parts.append(ASYNC_IO_JS)
|
||||||
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, has_parser, adapter_label, has_deps, has_router))
|
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router))
|
||||||
parts.append(EPILOGUE)
|
parts.append(EPILOGUE)
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
build_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
build_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
@@ -2042,15 +1999,6 @@ PREAMBLE = '''\
|
|||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
|
|
||||||
function StyleValue(className, declarations, mediaRules, pseudoRules, keyframes) {
|
|
||||||
this.className = className;
|
|
||||||
this.declarations = declarations || "";
|
|
||||||
this.mediaRules = mediaRules || [];
|
|
||||||
this.pseudoRules = pseudoRules || [];
|
|
||||||
this.keyframes = keyframes || [];
|
|
||||||
}
|
|
||||||
StyleValue.prototype._styleValue = true;
|
|
||||||
|
|
||||||
function isSym(x) { return x != null && x._sym === true; }
|
function isSym(x) { return x != null && x._sym === true; }
|
||||||
function isKw(x) { return x != null && x._kw === true; }
|
function isKw(x) { return x != null && x._kw === true; }
|
||||||
|
|
||||||
@@ -2247,30 +2195,6 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
|
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
|
||||||
''',
|
''',
|
||||||
|
|
||||||
"stdlib.style": '''
|
|
||||||
// stdlib.style
|
|
||||||
PRIMITIVES["css"] = function() {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return new StyleValue("sx-" + atoms.join("-"), atoms.join(";"), [], [], []);
|
|
||||||
};
|
|
||||||
PRIMITIVES["merge-styles"] = function() {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
var allDecls = valid.map(function(v) { return v.declarations; }).join(";");
|
|
||||||
return new StyleValue("sx-merged", allDecls, [], [], []);
|
|
||||||
};
|
|
||||||
''',
|
|
||||||
|
|
||||||
"stdlib.debug": '''
|
"stdlib.debug": '''
|
||||||
// stdlib.debug
|
// stdlib.debug
|
||||||
PRIMITIVES["assert"] = function(cond, msg) {
|
PRIMITIVES["assert"] = function(cond, msg) {
|
||||||
@@ -2318,7 +2242,6 @@ PLATFORM_JS_PRE = '''
|
|||||||
if (x._component) return "component";
|
if (x._component) return "component";
|
||||||
if (x._macro) return "macro";
|
if (x._macro) return "macro";
|
||||||
if (x._raw) return "raw-html";
|
if (x._raw) return "raw-html";
|
||||||
if (x._styleValue) return "style-value";
|
|
||||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||||
if (Array.isArray(x)) return "list";
|
if (Array.isArray(x)) return "list";
|
||||||
if (typeof x === "object") return "dict";
|
if (typeof x === "object") return "dict";
|
||||||
@@ -2366,27 +2289,6 @@ PLATFORM_JS_PRE = '''
|
|||||||
function isComponent(x) { return x != null && x._component === true; }
|
function isComponent(x) { return x != null && x._component === true; }
|
||||||
function isMacro(x) { return x != null && x._macro === true; }
|
function isMacro(x) { return x != null && x._macro === true; }
|
||||||
|
|
||||||
function isStyleValue(x) { return x != null && x._styleValue === true; }
|
|
||||||
function styleValueClass(x) { return x.className; }
|
|
||||||
function styleValue_p(x) { return x != null && x._styleValue === true; }
|
|
||||||
|
|
||||||
function buildKeyframes(kfName, steps, env) {
|
|
||||||
// Platform implementation of defkeyframes
|
|
||||||
var parts = [];
|
|
||||||
for (var i = 0; i < steps.length; i++) {
|
|
||||||
var step = steps[i];
|
|
||||||
var selector = isSym(step[0]) ? step[0].name : String(step[0]);
|
|
||||||
var body = trampoline(evalExpr(step[1], env));
|
|
||||||
var decls = isStyleValue(body) ? body.declarations : String(body);
|
|
||||||
parts.push(selector + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
var kfRule = "@keyframes " + kfName + "{" + parts.join("") + "}";
|
|
||||||
var cn = "sx-ref-kf-" + kfName;
|
|
||||||
var sv = new StyleValue(cn, "animation-name:" + kfName, [], [], [[kfName, kfRule]]);
|
|
||||||
env[kfName] = sv;
|
|
||||||
return sv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function envHas(env, name) { return name in env; }
|
function envHas(env, name) { return name in env; }
|
||||||
function envGet(env, name) { return env[name]; }
|
function envGet(env, name) { return env[name]; }
|
||||||
function envSet(env, name, val) { env[name] = val; }
|
function envSet(env, name, val) { env[name] = val; }
|
||||||
@@ -2512,7 +2414,7 @@ PLATFORM_JS_POST = '''
|
|||||||
function isSpecialForm(n) { return n in {
|
function isSpecialForm(n) { return n in {
|
||||||
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
||||||
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
||||||
"defkeyframes":1,"defhandler":1,"begin":1,"do":1,
|
"defhandler":1,"begin":1,"do":1,
|
||||||
"quote":1,"quasiquote":1,"->":1,"set!":1
|
"quote":1,"quasiquote":1,"->":1,"set!":1
|
||||||
}; }
|
}; }
|
||||||
function isHoForm(n) { return n in {
|
function isHoForm(n) { return n in {
|
||||||
@@ -2523,7 +2425,7 @@ PLATFORM_JS_POST = '''
|
|||||||
|
|
||||||
function isDefinitionForm(name) {
|
function isDefinitionForm(name) {
|
||||||
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
||||||
name === "defstyle" || name === "defkeyframes" || name === "defhandler";
|
name === "defstyle" || name === "defhandler";
|
||||||
}
|
}
|
||||||
|
|
||||||
function indexOf_(s, ch) {
|
function indexOf_(s, ch) {
|
||||||
@@ -2901,11 +2803,7 @@ PLATFORM_DOM_JS = """
|
|||||||
var attrVal = trampoline(evalExpr(args[i + 1], env));
|
var attrVal = trampoline(evalExpr(args[i + 1], env));
|
||||||
i++; // skip value
|
i++; // skip value
|
||||||
if (isNil(attrVal) || attrVal === false) continue;
|
if (isNil(attrVal) || attrVal === false) continue;
|
||||||
if (attrName === "class" && attrVal && attrVal._styleValue) {
|
if (contains(BOOLEAN_ATTRS, attrName)) {
|
||||||
extraClasses.push(attrVal.className);
|
|
||||||
} else if (attrName === "style" && attrVal && attrVal._styleValue) {
|
|
||||||
extraClasses.push(attrVal.className);
|
|
||||||
} else if (contains(BOOLEAN_ATTRS, attrName)) {
|
|
||||||
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
|
||||||
} else if (attrVal === true) {
|
} else if (attrVal === true) {
|
||||||
el.setAttribute(attrName, "");
|
el.setAttribute(attrName, "");
|
||||||
@@ -3787,102 +3685,6 @@ PLATFORM_ORCHESTRATION_JS = """
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLATFORM_CSSX_JS = """
|
|
||||||
// =========================================================================
|
|
||||||
// Platform interface — CSSX (style dictionary)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
function fnv1aHash(input) {
|
|
||||||
var h = 0x811c9dc5;
|
|
||||||
for (var i = 0; i < input.length; i++) {
|
|
||||||
h ^= input.charCodeAt(i);
|
|
||||||
h = (h * 0x01000193) >>> 0;
|
|
||||||
}
|
|
||||||
return h.toString(16).padStart(8, "0").substring(0, 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileRegex(pattern) {
|
|
||||||
try { return new RegExp(pattern); } catch (e) { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function regexMatch(re, s) {
|
|
||||||
if (!re) return NIL;
|
|
||||||
var m = s.match(re);
|
|
||||||
return m ? Array.prototype.slice.call(m) : NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function regexReplaceGroups(tmpl, match) {
|
|
||||||
var result = tmpl;
|
|
||||||
for (var j = 1; j < match.length; j++) {
|
|
||||||
result = result.split("{" + (j - 1) + "}").join(match[j]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeStyleValue_(cn, decls, media, pseudo, kf) {
|
|
||||||
return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function styleValueDeclarations(sv) { return sv.declarations; }
|
|
||||||
function styleValueMediaRules(sv) { return sv.mediaRules; }
|
|
||||||
function styleValuePseudoRules(sv) { return sv.pseudoRules; }
|
|
||||||
function styleValueKeyframes_(sv) { return sv.keyframes; }
|
|
||||||
|
|
||||||
function injectStyleValue(sv, atoms) {
|
|
||||||
if (_injectedStyles[sv.className]) return;
|
|
||||||
_injectedStyles[sv.className] = true;
|
|
||||||
|
|
||||||
if (!_hasDom) return;
|
|
||||||
var cssTarget = document.getElementById("sx-css");
|
|
||||||
if (!cssTarget) return;
|
|
||||||
|
|
||||||
var rules = [];
|
|
||||||
// Child-selector atoms are now routed to pseudoRules by the resolver
|
|
||||||
// with selector ">:not(:first-child)", so base declarations are always
|
|
||||||
// applied directly to the class.
|
|
||||||
if (sv.declarations) {
|
|
||||||
rules.push("." + sv.className + "{" + sv.declarations + "}");
|
|
||||||
}
|
|
||||||
for (var pi = 0; pi < sv.pseudoRules.length; pi++) {
|
|
||||||
var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1];
|
|
||||||
if (sel.indexOf("&") >= 0) {
|
|
||||||
rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}");
|
|
||||||
} else {
|
|
||||||
rules.push("." + sv.className + sel + "{" + decls + "}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var mi = 0; mi < sv.mediaRules.length; mi++) {
|
|
||||||
rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}");
|
|
||||||
}
|
|
||||||
for (var ki = 0; ki < sv.keyframes.length; ki++) {
|
|
||||||
rules.push(sv.keyframes[ki][1]);
|
|
||||||
}
|
|
||||||
cssTarget.textContent += rules.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace stub css primitive with real CSSX implementation
|
|
||||||
PRIMITIVES["css"] = function() {
|
|
||||||
var atoms = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var a = arguments[i];
|
|
||||||
if (isNil(a) || a === false) continue;
|
|
||||||
atoms.push(isKw(a) ? a.name : String(a));
|
|
||||||
}
|
|
||||||
if (!atoms.length) return NIL;
|
|
||||||
return resolveStyle(atoms);
|
|
||||||
};
|
|
||||||
|
|
||||||
PRIMITIVES["merge-styles"] = function() {
|
|
||||||
var valid = [];
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
|
|
||||||
}
|
|
||||||
if (!valid.length) return NIL;
|
|
||||||
if (valid.length === 1) return valid[0];
|
|
||||||
return mergeStyleValues(valid);
|
|
||||||
};
|
|
||||||
"""
|
|
||||||
|
|
||||||
PLATFORM_BOOT_JS = """
|
PLATFORM_BOOT_JS = """
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
||||||
@@ -3940,12 +3742,6 @@ PLATFORM_BOOT_JS = """
|
|||||||
r.querySelectorAll('script[type="text/sx"]'));
|
r.querySelectorAll('script[type="text/sx"]'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryStyleScripts() {
|
|
||||||
if (!_hasDom) return [];
|
|
||||||
return Array.prototype.slice.call(
|
|
||||||
document.querySelectorAll('script[type="text/sx-styles"]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryPageScripts() {
|
function queryPageScripts() {
|
||||||
if (!_hasDom) return [];
|
if (!_hasDom) return [];
|
||||||
return Array.prototype.slice.call(
|
return Array.prototype.slice.call(
|
||||||
@@ -3977,14 +3773,6 @@ PLATFORM_BOOT_JS = """
|
|||||||
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSxStylesCookie(hash) {
|
|
||||||
if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSxStylesCookie() {
|
|
||||||
if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax";
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Env helpers ---
|
// --- Env helpers ---
|
||||||
|
|
||||||
function parseEnvAttr(el) {
|
function parseEnvAttr(el) {
|
||||||
@@ -4033,10 +3821,6 @@ PLATFORM_BOOT_JS = """
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAndLoadStyleDict(text) {
|
|
||||||
try { loadStyleDict(JSON.parse(text)); }
|
|
||||||
catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); }
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def fixups_js(has_html, has_sx, has_dom):
|
def fixups_js(has_html, has_sx, has_dom):
|
||||||
@@ -4063,7 +3847,7 @@ def fixups_js(has_html, has_sx, has_dom):
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, has_parser, adapter_label, has_deps=False, has_router=False):
|
def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps=False, has_router=False):
|
||||||
# Parser: use compiled sxParse from parser.sx, or inline a minimal fallback
|
# Parser: use compiled sxParse from parser.sx, or inline a minimal fallback
|
||||||
if has_parser:
|
if has_parser:
|
||||||
parser = '''
|
parser = '''
|
||||||
|
|||||||
@@ -214,11 +214,6 @@ class PyEmitter:
|
|||||||
"ho-every": "ho_every",
|
"ho-every": "ho_every",
|
||||||
"ho-for-each": "ho_for_each",
|
"ho-for-each": "ho_for_each",
|
||||||
"sf-defstyle": "sf_defstyle",
|
"sf-defstyle": "sf_defstyle",
|
||||||
"sf-defkeyframes": "sf_defkeyframes",
|
|
||||||
"build-keyframes": "build_keyframes",
|
|
||||||
"style-value?": "is_style_value",
|
|
||||||
"style-value-class": "style_value_class",
|
|
||||||
"kf-name": "kf_name",
|
|
||||||
"special-form?": "is_special_form",
|
"special-form?": "is_special_form",
|
||||||
"ho-form?": "is_ho_form",
|
"ho-form?": "is_ho_form",
|
||||||
"strip-prefix": "strip_prefix",
|
"strip-prefix": "strip_prefix",
|
||||||
@@ -1097,7 +1092,7 @@ from typing import Any
|
|||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
from shared.sx.types import (
|
from shared.sx.types import (
|
||||||
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro, StyleValue,
|
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro,
|
||||||
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
|
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
|
||||||
)
|
)
|
||||||
from shared.sx.parser import SxExpr
|
from shared.sx.parser import SxExpr
|
||||||
@@ -1206,8 +1201,6 @@ def type_of(x):
|
|||||||
return "macro"
|
return "macro"
|
||||||
if isinstance(x, _RawHTML):
|
if isinstance(x, _RawHTML):
|
||||||
return "raw-html"
|
return "raw-html"
|
||||||
if isinstance(x, StyleValue):
|
|
||||||
return "style-value"
|
|
||||||
if isinstance(x, Continuation):
|
if isinstance(x, Continuation):
|
||||||
return "continuation"
|
return "continuation"
|
||||||
if isinstance(x, list):
|
if isinstance(x, list):
|
||||||
@@ -1376,14 +1369,6 @@ def is_macro(x):
|
|||||||
return isinstance(x, Macro)
|
return isinstance(x, Macro)
|
||||||
|
|
||||||
|
|
||||||
def is_style_value(x):
|
|
||||||
return isinstance(x, StyleValue)
|
|
||||||
|
|
||||||
|
|
||||||
def style_value_class(x):
|
|
||||||
return x.class_name
|
|
||||||
|
|
||||||
|
|
||||||
def env_has(env, name):
|
def env_has(env, name):
|
||||||
return name in env
|
return name in env
|
||||||
|
|
||||||
@@ -1534,8 +1519,6 @@ def serialize(val):
|
|||||||
if t == "raw-html":
|
if t == "raw-html":
|
||||||
escaped = escape_string(raw_html_content(val))
|
escaped = escape_string(raw_html_content(val))
|
||||||
return '(raw! "' + escaped + '")'
|
return '(raw! "' + escaped + '")'
|
||||||
if t == "style-value":
|
|
||||||
return '"' + style_value_class(val) + '"'
|
|
||||||
if t == "list":
|
if t == "list":
|
||||||
if not val:
|
if not val:
|
||||||
return "()"
|
return "()"
|
||||||
@@ -1555,7 +1538,7 @@ def serialize(val):
|
|||||||
_SPECIAL_FORM_NAMES = frozenset([
|
_SPECIAL_FORM_NAMES = frozenset([
|
||||||
"if", "when", "cond", "case", "and", "or",
|
"if", "when", "cond", "case", "and", "or",
|
||||||
"let", "let*", "lambda", "fn",
|
"let", "let*", "lambda", "fn",
|
||||||
"define", "defcomp", "defmacro", "defstyle", "defkeyframes",
|
"define", "defcomp", "defmacro", "defstyle",
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
||||||
"begin", "do", "quote", "quasiquote",
|
"begin", "do", "quote", "quasiquote",
|
||||||
"->", "set!",
|
"->", "set!",
|
||||||
@@ -1717,7 +1700,7 @@ def aser_special(name, expr, env):
|
|||||||
fn(item)
|
fn(item)
|
||||||
return results if results else NIL
|
return results if results else NIL
|
||||||
# Definition forms — evaluate for side effects
|
# Definition forms — evaluate for side effects
|
||||||
if name in ("define", "defcomp", "defmacro", "defstyle", "defkeyframes",
|
if name in ("define", "defcomp", "defmacro", "defstyle",
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
||||||
trampoline(eval_expr(expr, env))
|
trampoline(eval_expr(expr, env))
|
||||||
return NIL
|
return NIL
|
||||||
|
|||||||
@@ -123,4 +123,4 @@
|
|||||||
|
|
||||||
(define-boundary-types
|
(define-boundary-types
|
||||||
(list "number" "string" "boolean" "nil" "keyword"
|
(list "number" "string" "boolean" "nil" "keyword"
|
||||||
"list" "dict" "sx-source" "style-value"))
|
"list" "dict" "sx-source"))
|
||||||
|
|||||||
@@ -1,317 +0,0 @@
|
|||||||
;; ==========================================================================
|
|
||||||
;; cssx.sx — On-demand CSS style dictionary
|
|
||||||
;;
|
|
||||||
;; Resolves keyword atoms (e.g. :flex, :gap-4, :hover:bg-sky-200) into
|
|
||||||
;; StyleValue objects with content-addressed class names. CSS rules are
|
|
||||||
;; injected into the document on first use.
|
|
||||||
;;
|
|
||||||
;; The style dictionary is loaded from a JSON blob (typically served
|
|
||||||
;; inline in a <script type="text/sx-styles"> tag) containing:
|
|
||||||
;; a — atom → CSS declarations map
|
|
||||||
;; v — pseudo-variant → CSS pseudo-selector map
|
|
||||||
;; b — responsive breakpoint → media query map
|
|
||||||
;; k — keyframe name → @keyframes rule map
|
|
||||||
;; p — arbitrary patterns: [[regex, template], ...]
|
|
||||||
;; c — child selector prefixes: ["space-x-", "space-y-", ...]
|
|
||||||
;;
|
|
||||||
;; Depends on:
|
|
||||||
;; render.sx — StyleValue type
|
|
||||||
;; ==========================================================================
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; State — populated by load-style-dict
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define _style-atoms (dict))
|
|
||||||
(define _pseudo-variants (dict))
|
|
||||||
(define _responsive-breakpoints (dict))
|
|
||||||
(define _style-keyframes (dict))
|
|
||||||
(define _arbitrary-patterns (list))
|
|
||||||
(define _child-selector-prefixes (list))
|
|
||||||
(define _style-cache (dict))
|
|
||||||
(define _injected-styles (dict))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Load style dictionary from parsed JSON data
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define load-style-dict
|
|
||||||
(fn (data)
|
|
||||||
(set! _style-atoms (or (get data "a") (dict)))
|
|
||||||
(set! _pseudo-variants (or (get data "v") (dict)))
|
|
||||||
(set! _responsive-breakpoints (or (get data "b") (dict)))
|
|
||||||
(set! _style-keyframes (or (get data "k") (dict)))
|
|
||||||
(set! _child-selector-prefixes (or (get data "c") (list)))
|
|
||||||
;; Compile arbitrary patterns from [regex, template] pairs
|
|
||||||
(set! _arbitrary-patterns
|
|
||||||
(map
|
|
||||||
(fn (pair)
|
|
||||||
(dict "re" (compile-regex (str "^" (first pair) "$"))
|
|
||||||
"tmpl" (nth pair 1)))
|
|
||||||
(or (get data "p") (list))))
|
|
||||||
;; Clear cache on reload
|
|
||||||
(set! _style-cache (dict))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Variant splitting
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define split-variant
|
|
||||||
(fn (atom)
|
|
||||||
;; Parse variant prefixes: "sm:hover:bg-sky-200" → ["sm:hover", "bg-sky-200"]
|
|
||||||
;; Returns [variant, base] where variant is nil for no variant.
|
|
||||||
|
|
||||||
;; Check responsive prefix first
|
|
||||||
(let ((result nil))
|
|
||||||
(for-each
|
|
||||||
(fn (bp)
|
|
||||||
(when (nil? result)
|
|
||||||
(let ((prefix (str bp ":")))
|
|
||||||
(when (starts-with? atom prefix)
|
|
||||||
(let ((rest-atom (slice atom (len prefix))))
|
|
||||||
;; Check for compound variant (sm:hover:...)
|
|
||||||
(let ((inner-match nil))
|
|
||||||
(for-each
|
|
||||||
(fn (pv)
|
|
||||||
(when (nil? inner-match)
|
|
||||||
(let ((inner-prefix (str pv ":")))
|
|
||||||
(when (starts-with? rest-atom inner-prefix)
|
|
||||||
(set! inner-match
|
|
||||||
(list (str bp ":" pv)
|
|
||||||
(slice rest-atom (len inner-prefix))))))))
|
|
||||||
(keys _pseudo-variants))
|
|
||||||
(set! result
|
|
||||||
(or inner-match (list bp rest-atom)))))))))
|
|
||||||
(keys _responsive-breakpoints))
|
|
||||||
|
|
||||||
(when (nil? result)
|
|
||||||
;; Check pseudo variants
|
|
||||||
(for-each
|
|
||||||
(fn (pv)
|
|
||||||
(when (nil? result)
|
|
||||||
(let ((prefix (str pv ":")))
|
|
||||||
(when (starts-with? atom prefix)
|
|
||||||
(set! result (list pv (slice atom (len prefix))))))))
|
|
||||||
(keys _pseudo-variants)))
|
|
||||||
|
|
||||||
(or result (list nil atom)))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Atom resolution
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define resolve-atom
|
|
||||||
(fn (atom)
|
|
||||||
;; Look up atom → CSS declarations string, or nil
|
|
||||||
(let ((decls (dict-get _style-atoms atom)))
|
|
||||||
(if (not (nil? decls))
|
|
||||||
decls
|
|
||||||
;; Dynamic keyframes: animate-{name}
|
|
||||||
(if (starts-with? atom "animate-")
|
|
||||||
(let ((kf-name (slice atom 8)))
|
|
||||||
(if (dict-has? _style-keyframes kf-name)
|
|
||||||
(str "animation-name:" kf-name)
|
|
||||||
nil))
|
|
||||||
;; Try arbitrary patterns
|
|
||||||
(let ((match-result nil))
|
|
||||||
(for-each
|
|
||||||
(fn (pat)
|
|
||||||
(when (nil? match-result)
|
|
||||||
(let ((m (regex-match (get pat "re") atom)))
|
|
||||||
(when m
|
|
||||||
(set! match-result
|
|
||||||
(regex-replace-groups (get pat "tmpl") m))))))
|
|
||||||
_arbitrary-patterns)
|
|
||||||
match-result))))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Child selector detection
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define is-child-selector-atom?
|
|
||||||
(fn (atom)
|
|
||||||
(some
|
|
||||||
(fn (prefix) (starts-with? atom prefix))
|
|
||||||
_child-selector-prefixes)))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; FNV-1a 32-bit hash → 6 hex chars
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define hash-style
|
|
||||||
(fn (input)
|
|
||||||
;; FNV-1a 32-bit hash for content-addressed class names
|
|
||||||
(fnv1a-hash input)))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Full style resolution pipeline
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define resolve-style
|
|
||||||
(fn (atoms)
|
|
||||||
;; Resolve a list of atom strings into a StyleValue.
|
|
||||||
;; Uses content-addressed caching.
|
|
||||||
(let ((key (join "\0" atoms)))
|
|
||||||
(let ((cached (dict-get _style-cache key)))
|
|
||||||
(if (not (nil? cached))
|
|
||||||
cached
|
|
||||||
;; Resolve each atom
|
|
||||||
(let ((base-decls (list))
|
|
||||||
(media-rules (list))
|
|
||||||
(pseudo-rules (list))
|
|
||||||
(kf-needed (list)))
|
|
||||||
(for-each
|
|
||||||
(fn (a)
|
|
||||||
(when a
|
|
||||||
(let ((clean (if (starts-with? a ":") (slice a 1) a)))
|
|
||||||
(let ((parts (split-variant clean)))
|
|
||||||
(let ((variant (first parts))
|
|
||||||
(base (nth parts 1))
|
|
||||||
(decls (resolve-atom base)))
|
|
||||||
(when decls
|
|
||||||
;; Check keyframes
|
|
||||||
(when (starts-with? base "animate-")
|
|
||||||
(let ((kf-name (slice base 8)))
|
|
||||||
(when (dict-has? _style-keyframes kf-name)
|
|
||||||
(append! kf-needed
|
|
||||||
(list kf-name (dict-get _style-keyframes kf-name))))))
|
|
||||||
|
|
||||||
(cond
|
|
||||||
(nil? variant)
|
|
||||||
(if (is-child-selector-atom? base)
|
|
||||||
(append! pseudo-rules
|
|
||||||
(list ">:not(:first-child)" decls))
|
|
||||||
(append! base-decls decls))
|
|
||||||
|
|
||||||
(dict-has? _responsive-breakpoints variant)
|
|
||||||
(append! media-rules
|
|
||||||
(list (dict-get _responsive-breakpoints variant) decls))
|
|
||||||
|
|
||||||
(dict-has? _pseudo-variants variant)
|
|
||||||
(append! pseudo-rules
|
|
||||||
(list (dict-get _pseudo-variants variant) decls))
|
|
||||||
|
|
||||||
;; Compound variant: "sm:hover"
|
|
||||||
:else
|
|
||||||
(let ((vparts (split variant ":"))
|
|
||||||
(media-part nil)
|
|
||||||
(pseudo-part nil))
|
|
||||||
(for-each
|
|
||||||
(fn (vp)
|
|
||||||
(cond
|
|
||||||
(dict-has? _responsive-breakpoints vp)
|
|
||||||
(set! media-part (dict-get _responsive-breakpoints vp))
|
|
||||||
(dict-has? _pseudo-variants vp)
|
|
||||||
(set! pseudo-part (dict-get _pseudo-variants vp))))
|
|
||||||
vparts)
|
|
||||||
(when media-part
|
|
||||||
(append! media-rules (list media-part decls)))
|
|
||||||
(when pseudo-part
|
|
||||||
(append! pseudo-rules (list pseudo-part decls)))
|
|
||||||
(when (and (nil? media-part) (nil? pseudo-part))
|
|
||||||
(append! base-decls decls))))))))))
|
|
||||||
atoms)
|
|
||||||
|
|
||||||
;; Build hash input
|
|
||||||
(let ((hash-input (join ";" base-decls)))
|
|
||||||
(for-each
|
|
||||||
(fn (mr)
|
|
||||||
(set! hash-input
|
|
||||||
(str hash-input "@" (first mr) "{" (nth mr 1) "}")))
|
|
||||||
media-rules)
|
|
||||||
(for-each
|
|
||||||
(fn (pr)
|
|
||||||
(set! hash-input
|
|
||||||
(str hash-input (first pr) "{" (nth pr 1) "}")))
|
|
||||||
pseudo-rules)
|
|
||||||
(for-each
|
|
||||||
(fn (kf)
|
|
||||||
(set! hash-input (str hash-input (nth kf 1))))
|
|
||||||
kf-needed)
|
|
||||||
|
|
||||||
(let ((cn (str "sx-" (hash-style hash-input)))
|
|
||||||
(sv (make-style-value cn
|
|
||||||
(join ";" base-decls)
|
|
||||||
media-rules
|
|
||||||
pseudo-rules
|
|
||||||
kf-needed)))
|
|
||||||
(dict-set! _style-cache key sv)
|
|
||||||
;; Inject CSS rules
|
|
||||||
(inject-style-value sv atoms)
|
|
||||||
sv))))))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Merge multiple StyleValues
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define merge-style-values
|
|
||||||
(fn (styles)
|
|
||||||
(if (= (len styles) 1)
|
|
||||||
(first styles)
|
|
||||||
(let ((all-decls (list))
|
|
||||||
(all-media (list))
|
|
||||||
(all-pseudo (list))
|
|
||||||
(all-kf (list)))
|
|
||||||
(for-each
|
|
||||||
(fn (sv)
|
|
||||||
(when (style-value-declarations sv)
|
|
||||||
(append! all-decls (style-value-declarations sv)))
|
|
||||||
(set! all-media (concat all-media (style-value-media-rules sv)))
|
|
||||||
(set! all-pseudo (concat all-pseudo (style-value-pseudo-rules sv)))
|
|
||||||
(set! all-kf (concat all-kf (style-value-keyframes sv))))
|
|
||||||
styles)
|
|
||||||
|
|
||||||
(let ((hash-input (join ";" all-decls)))
|
|
||||||
(for-each
|
|
||||||
(fn (mr)
|
|
||||||
(set! hash-input
|
|
||||||
(str hash-input "@" (first mr) "{" (nth mr 1) "}")))
|
|
||||||
all-media)
|
|
||||||
(for-each
|
|
||||||
(fn (pr)
|
|
||||||
(set! hash-input
|
|
||||||
(str hash-input (first pr) "{" (nth pr 1) "}")))
|
|
||||||
all-pseudo)
|
|
||||||
(for-each
|
|
||||||
(fn (kf)
|
|
||||||
(set! hash-input (str hash-input (nth kf 1))))
|
|
||||||
all-kf)
|
|
||||||
|
|
||||||
(let ((cn (str "sx-" (hash-style hash-input)))
|
|
||||||
(merged (make-style-value cn
|
|
||||||
(join ";" all-decls)
|
|
||||||
all-media all-pseudo all-kf)))
|
|
||||||
(inject-style-value merged (list))
|
|
||||||
merged))))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;; Platform interface — CSSX
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
;;
|
|
||||||
;; Hash:
|
|
||||||
;; (fnv1a-hash input) → 6-char hex string (FNV-1a 32-bit)
|
|
||||||
;;
|
|
||||||
;; Regex:
|
|
||||||
;; (compile-regex pattern) → compiled regex object
|
|
||||||
;; (regex-match re str) → match array or nil
|
|
||||||
;; (regex-replace-groups tmpl match) → string with {0},{1},... replaced
|
|
||||||
;;
|
|
||||||
;; StyleValue construction:
|
|
||||||
;; (make-style-value cn decls media pseudo kf) → StyleValue object
|
|
||||||
;; (style-value-declarations sv) → declarations string
|
|
||||||
;; (style-value-media-rules sv) → list of [query, decls] pairs
|
|
||||||
;; (style-value-pseudo-rules sv) → list of [selector, decls] pairs
|
|
||||||
;; (style-value-keyframes sv) → list of [name, rule] pairs
|
|
||||||
;;
|
|
||||||
;; CSS injection:
|
|
||||||
;; (inject-style-value sv atoms) → void (append CSS rules to <style id="sx-css">)
|
|
||||||
;; --------------------------------------------------------------------------
|
|
||||||
@@ -143,7 +143,6 @@
|
|||||||
(= name "defcomp") (sf-defcomp args env)
|
(= name "defcomp") (sf-defcomp args env)
|
||||||
(= name "defmacro") (sf-defmacro args env)
|
(= name "defmacro") (sf-defmacro args env)
|
||||||
(= name "defstyle") (sf-defstyle args env)
|
(= name "defstyle") (sf-defstyle args env)
|
||||||
(= name "defkeyframes") (sf-defkeyframes args env)
|
|
||||||
(= name "defhandler") (sf-defhandler args env)
|
(= name "defhandler") (sf-defhandler args env)
|
||||||
(= name "defpage") (sf-defpage args env)
|
(= name "defpage") (sf-defpage args env)
|
||||||
(= name "defquery") (sf-defquery args env)
|
(= name "defquery") (sf-defquery args env)
|
||||||
@@ -579,23 +578,13 @@
|
|||||||
|
|
||||||
(define sf-defstyle
|
(define sf-defstyle
|
||||||
(fn (args env)
|
(fn (args env)
|
||||||
;; (defstyle name expr) — bind name to evaluated expr (typically a StyleValue)
|
;; (defstyle name expr) — bind name to evaluated expr (string, function, etc.)
|
||||||
(let ((name-sym (first args))
|
(let ((name-sym (first args))
|
||||||
(value (trampoline (eval-expr (nth args 1) env))))
|
(value (trampoline (eval-expr (nth args 1) env))))
|
||||||
(env-set! env (symbol-name name-sym) value)
|
(env-set! env (symbol-name name-sym) value)
|
||||||
value)))
|
value)))
|
||||||
|
|
||||||
|
|
||||||
(define sf-defkeyframes
|
|
||||||
(fn (args env)
|
|
||||||
;; (defkeyframes name (selector body) ...) — build @keyframes rule,
|
|
||||||
;; register in keyframes dict, return StyleValue.
|
|
||||||
;; Delegates to platform: build-keyframes returns a StyleValue.
|
|
||||||
(let ((kf-name (symbol-name (first args)))
|
|
||||||
(steps (rest args)))
|
|
||||||
(build-keyframes kf-name steps env))))
|
|
||||||
|
|
||||||
|
|
||||||
(define sf-begin
|
(define sf-begin
|
||||||
(fn (args env)
|
(fn (args env)
|
||||||
(if (empty? args)
|
(if (empty? args)
|
||||||
@@ -952,9 +941,6 @@
|
|||||||
;; (zip lists...) → list of tuples
|
;; (zip lists...) → list of tuples
|
||||||
;;
|
;;
|
||||||
;;
|
;;
|
||||||
;; CSSX (style system):
|
|
||||||
;; (build-keyframes name steps env) → StyleValue (platform builds @keyframes)
|
|
||||||
;;
|
|
||||||
;; Dynamic wind (for dynamic-wind):
|
;; Dynamic wind (for dynamic-wind):
|
||||||
;; (push-wind! before after) → void (push wind record onto stack)
|
;; (push-wind! before after) → void (push wind record onto stack)
|
||||||
;; (pop-wind!) → void (pop wind record from stack)
|
;; (pop-wind!) → void (pop wind record from stack)
|
||||||
|
|||||||
@@ -541,18 +541,6 @@
|
|||||||
;; Stdlib — Style
|
;; Stdlib — Style
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
(define-module :stdlib.style)
|
|
||||||
|
|
||||||
(define-primitive "css"
|
|
||||||
:params (&rest atoms)
|
|
||||||
:returns "style-value"
|
|
||||||
:doc "Resolve style atoms to a StyleValue with className and CSS declarations.
|
|
||||||
Atoms are keywords or strings: (css :flex :gap-4 :hover:bg-sky-200).")
|
|
||||||
|
|
||||||
(define-primitive "merge-styles"
|
|
||||||
:params (&rest styles)
|
|
||||||
:returns "style-value"
|
|
||||||
:doc "Merge multiple StyleValues into one combined StyleValue.")
|
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
(define definition-form?
|
(define definition-form?
|
||||||
(fn (name)
|
(fn (name)
|
||||||
(or (= name "define") (= name "defcomp") (= name "defmacro")
|
(or (= name "define") (= name "defcomp") (= name "defmacro")
|
||||||
(= name "defstyle") (= name "defkeyframes") (= name "defhandler"))))
|
(= name "defstyle") (= name "defhandler"))))
|
||||||
|
|
||||||
|
|
||||||
(define parse-element-args
|
(define parse-element-args
|
||||||
@@ -116,9 +116,6 @@
|
|||||||
""
|
""
|
||||||
;; Nil values — skip
|
;; Nil values — skip
|
||||||
(nil? val) ""
|
(nil? val) ""
|
||||||
;; StyleValue on :style → emit as class
|
|
||||||
(and (= key "style") (style-value? val))
|
|
||||||
(str " class=\"" (style-value-class val) "\"")
|
|
||||||
;; Normal attr
|
;; Normal attr
|
||||||
:else (str " " key "=\"" (escape-attr (str val)) "\""))))
|
:else (str " " key "=\"" (escape-attr (str val)) "\""))))
|
||||||
(keys attrs)))))
|
(keys attrs)))))
|
||||||
@@ -202,10 +199,6 @@
|
|||||||
;; (escape-attr s) → attribute-value-escaped string
|
;; (escape-attr s) → attribute-value-escaped string
|
||||||
;; (raw-html-content r) → unwrap RawHTML marker to string
|
;; (raw-html-content r) → unwrap RawHTML marker to string
|
||||||
;;
|
;;
|
||||||
;; StyleValue:
|
|
||||||
;; (style-value? x) → boolean (is x a StyleValue?)
|
|
||||||
;; (style-value-class sv) → string (CSS class name)
|
|
||||||
;;
|
|
||||||
;; Serialization:
|
;; Serialization:
|
||||||
;; (serialize val) → SX source string representation of val
|
;; (serialize val) → SX source string representation of val
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -363,20 +363,12 @@
|
|||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
(define-special-form "defstyle"
|
(define-special-form "defstyle"
|
||||||
:syntax (defstyle name atoms ...)
|
:syntax (defstyle name expr)
|
||||||
:doc "Define a named style. Evaluates atoms to a StyleValue and binds
|
:doc "Define a named style value. Evaluates expr and binds the result
|
||||||
it to name in the environment."
|
to name in the environment. The value is typically a class string
|
||||||
|
or a function that returns class strings."
|
||||||
:tail-position "none"
|
:tail-position "none"
|
||||||
:example "(defstyle card-style :rounded-lg :shadow-md :p-4 :bg-white)")
|
:example "(defstyle card-style \"rounded-lg shadow-md p-4 bg-white\")")
|
||||||
|
|
||||||
(define-special-form "defkeyframes"
|
|
||||||
:syntax (defkeyframes name steps ...)
|
|
||||||
:doc "Define a CSS @keyframes animation. Steps are (percentage properties ...)
|
|
||||||
pairs. Produces a StyleValue with the animation name and keyframe rules."
|
|
||||||
:tail-position "none"
|
|
||||||
:example "(defkeyframes fade-in
|
|
||||||
(0 :opacity-0)
|
|
||||||
(100 :opacity-100))")
|
|
||||||
|
|
||||||
(define-special-form "defhandler"
|
(define-special-form "defhandler"
|
||||||
:syntax (defhandler name (&key params ...) body)
|
:syntax (defhandler name (&key params ...) body)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from typing import Any
|
|||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
from shared.sx.types import (
|
from shared.sx.types import (
|
||||||
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro, StyleValue,
|
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro,
|
||||||
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
|
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
|
||||||
)
|
)
|
||||||
from shared.sx.parser import SxExpr
|
from shared.sx.parser import SxExpr
|
||||||
@@ -126,8 +126,6 @@ def type_of(x):
|
|||||||
return "macro"
|
return "macro"
|
||||||
if isinstance(x, _RawHTML):
|
if isinstance(x, _RawHTML):
|
||||||
return "raw-html"
|
return "raw-html"
|
||||||
if isinstance(x, StyleValue):
|
|
||||||
return "style-value"
|
|
||||||
if isinstance(x, Continuation):
|
if isinstance(x, Continuation):
|
||||||
return "continuation"
|
return "continuation"
|
||||||
if isinstance(x, list):
|
if isinstance(x, list):
|
||||||
@@ -296,14 +294,6 @@ def is_macro(x):
|
|||||||
return isinstance(x, Macro)
|
return isinstance(x, Macro)
|
||||||
|
|
||||||
|
|
||||||
def is_style_value(x):
|
|
||||||
return isinstance(x, StyleValue)
|
|
||||||
|
|
||||||
|
|
||||||
def style_value_class(x):
|
|
||||||
return x.class_name
|
|
||||||
|
|
||||||
|
|
||||||
def env_has(env, name):
|
def env_has(env, name):
|
||||||
return name in env
|
return name in env
|
||||||
|
|
||||||
@@ -454,8 +444,6 @@ def serialize(val):
|
|||||||
if t == "raw-html":
|
if t == "raw-html":
|
||||||
escaped = escape_string(raw_html_content(val))
|
escaped = escape_string(raw_html_content(val))
|
||||||
return '(raw! "' + escaped + '")'
|
return '(raw! "' + escaped + '")'
|
||||||
if t == "style-value":
|
|
||||||
return '"' + style_value_class(val) + '"'
|
|
||||||
if t == "list":
|
if t == "list":
|
||||||
if not val:
|
if not val:
|
||||||
return "()"
|
return "()"
|
||||||
@@ -475,7 +463,7 @@ def serialize(val):
|
|||||||
_SPECIAL_FORM_NAMES = frozenset([
|
_SPECIAL_FORM_NAMES = frozenset([
|
||||||
"if", "when", "cond", "case", "and", "or",
|
"if", "when", "cond", "case", "and", "or",
|
||||||
"let", "let*", "lambda", "fn",
|
"let", "let*", "lambda", "fn",
|
||||||
"define", "defcomp", "defmacro", "defstyle", "defkeyframes",
|
"define", "defcomp", "defmacro", "defstyle",
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
||||||
"begin", "do", "quote", "quasiquote",
|
"begin", "do", "quote", "quasiquote",
|
||||||
"->", "set!",
|
"->", "set!",
|
||||||
@@ -637,7 +625,7 @@ def aser_special(name, expr, env):
|
|||||||
fn(item)
|
fn(item)
|
||||||
return results if results else NIL
|
return results if results else NIL
|
||||||
# Definition forms — evaluate for side effects
|
# Definition forms — evaluate for side effects
|
||||||
if name in ("define", "defcomp", "defmacro", "defstyle", "defkeyframes",
|
if name in ("define", "defcomp", "defmacro", "defstyle",
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
||||||
trampoline(eval_expr(expr, env))
|
trampoline(eval_expr(expr, env))
|
||||||
return NIL
|
return NIL
|
||||||
@@ -959,7 +947,7 @@ trampoline = lambda val: (lambda result: (trampoline(eval_expr(thunk_expr(result
|
|||||||
eval_expr = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('dict', lambda: map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else eval_list(expr, env))), (None, lambda: expr)])
|
eval_expr = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('dict', lambda: map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else eval_list(expr, env))), (None, lambda: expr)])
|
||||||
|
|
||||||
# eval-list
|
# eval-list
|
||||||
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_letrec(args, env) if sx_truthy((name == 'letrec')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defkeyframes(args, env) if sx_truthy((name == 'defkeyframes')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (sf_reset(args, env) if sx_truthy((name == 'reset')) else (sf_shift(args, env) if sx_truthy((name == 'shift')) else (sf_dynamic_wind(args, env) if sx_truthy((name == 'dynamic-wind')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env))))))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
|
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_letrec(args, env) if sx_truthy((name == 'letrec')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (sf_reset(args, env) if sx_truthy((name == 'reset')) else (sf_shift(args, env) if sx_truthy((name == 'shift')) else (sf_dynamic_wind(args, env) if sx_truthy((name == 'dynamic-wind')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env)))))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
|
||||||
|
|
||||||
# eval-call
|
# eval-call
|
||||||
eval_call = lambda head, args, env: (lambda f: (lambda evaluated_args: (apply(f, evaluated_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))) else (call_lambda(f, evaluated_args, env) if sx_truthy(is_lambda(f)) else (call_component(f, args, env) if sx_truthy(is_component(f)) else error(sx_str('Not callable: ', inspect(f)))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))
|
eval_call = lambda head, args, env: (lambda f: (lambda evaluated_args: (apply(f, evaluated_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))) else (call_lambda(f, evaluated_args, env) if sx_truthy(is_lambda(f)) else (call_component(f, args, env) if sx_truthy(is_component(f)) else error(sx_str('Not callable: ', inspect(f)))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))
|
||||||
@@ -1066,9 +1054,6 @@ def parse_macro_params(params_expr):
|
|||||||
# sf-defstyle
|
# sf-defstyle
|
||||||
sf_defstyle = lambda args, env: (lambda name_sym: (lambda value: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args))
|
sf_defstyle = lambda args, env: (lambda name_sym: (lambda value: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args))
|
||||||
|
|
||||||
# sf-defkeyframes
|
|
||||||
sf_defkeyframes = lambda args, env: (lambda kf_name: (lambda steps: build_keyframes(kf_name, steps, env))(rest(args)))(symbol_name(first(args)))
|
|
||||||
|
|
||||||
# sf-begin
|
# sf-begin
|
||||||
sf_begin = lambda args, env: (NIL if sx_truthy(empty_p(args)) else _sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 0, (len(args) - 1))), make_thunk(last(args), env)))
|
sf_begin = lambda args, env: (NIL if sx_truthy(empty_p(args)) else _sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 0, (len(args) - 1))), make_thunk(last(args), env)))
|
||||||
|
|
||||||
@@ -1179,13 +1164,13 @@ VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'li
|
|||||||
BOOLEAN_ATTRS = ['async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'inert', 'ismap', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected']
|
BOOLEAN_ATTRS = ['async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'inert', 'ismap', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected']
|
||||||
|
|
||||||
# definition-form?
|
# definition-form?
|
||||||
is_definition_form = lambda name: ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defkeyframes') if sx_truthy((name == 'defkeyframes')) else (name == 'defhandler'))))))
|
is_definition_form = lambda name: ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else (name == 'defhandler')))))
|
||||||
|
|
||||||
# parse-element-args
|
# parse-element-args
|
||||||
parse_element_args = lambda args, env: (lambda attrs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), [attrs, children]))([]))({})
|
parse_element_args = lambda args, env: (lambda attrs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), [attrs, children]))([]))({})
|
||||||
|
|
||||||
# render-attrs
|
# render-attrs
|
||||||
render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else (sx_str(' class="', style_value_class(val), '"') if sx_truthy(((key == 'style') if not sx_truthy((key == 'style')) else is_style_value(val))) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"'))))))(dict_get(attrs, key)), keys(attrs)))
|
render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"')))))(dict_get(attrs, key)), keys(attrs)))
|
||||||
|
|
||||||
# eval-cond
|
# eval-cond
|
||||||
eval_cond = lambda clauses, env: (eval_cond_scheme(clauses, env) if sx_truthy(((not sx_truthy(empty_p(clauses))) if not sx_truthy((not sx_truthy(empty_p(clauses)))) else ((type_of(first(clauses)) == 'list') if not sx_truthy((type_of(first(clauses)) == 'list')) else (len(first(clauses)) == 2)))) else eval_cond_clojure(clauses, env))
|
eval_cond = lambda clauses, env: (eval_cond_scheme(clauses, env) if sx_truthy(((not sx_truthy(empty_p(clauses))) if not sx_truthy((not sx_truthy(empty_p(clauses)))) else ((type_of(first(clauses)) == 'list') if not sx_truthy((type_of(first(clauses)) == 'list')) else (len(first(clauses)) == 2)))) else eval_cond_clojure(clauses, env))
|
||||||
@@ -1206,10 +1191,10 @@ process_bindings = lambda bindings, env: (lambda local: _sx_begin(for_each(lambd
|
|||||||
render_to_html = lambda expr, env: _sx_case(type_of(expr), [('nil', lambda: ''), ('string', lambda: escape_html(expr)), ('number', lambda: sx_str(expr)), ('boolean', lambda: ('true' if sx_truthy(expr) else 'false')), ('list', lambda: ('' if sx_truthy(empty_p(expr)) else render_list_to_html(expr, env))), ('symbol', lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env)), ('keyword', lambda: escape_html(keyword_name(expr))), ('raw-html', lambda: raw_html_content(expr)), (None, lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env))])
|
render_to_html = lambda expr, env: _sx_case(type_of(expr), [('nil', lambda: ''), ('string', lambda: escape_html(expr)), ('number', lambda: sx_str(expr)), ('boolean', lambda: ('true' if sx_truthy(expr) else 'false')), ('list', lambda: ('' if sx_truthy(empty_p(expr)) else render_list_to_html(expr, env))), ('symbol', lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env)), ('keyword', lambda: escape_html(keyword_name(expr))), ('raw-html', lambda: raw_html_content(expr)), (None, lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env))])
|
||||||
|
|
||||||
# render-value-to-html
|
# render-value-to-html
|
||||||
render_value_to_html = lambda val, env: _sx_case(type_of(val), [('nil', lambda: ''), ('string', lambda: escape_html(val)), ('number', lambda: sx_str(val)), ('boolean', lambda: ('true' if sx_truthy(val) else 'false')), ('list', lambda: render_list_to_html(val, env)), ('raw-html', lambda: raw_html_content(val)), ('style-value', lambda: style_value_class(val)), (None, lambda: escape_html(sx_str(val)))])
|
render_value_to_html = lambda val, env: _sx_case(type_of(val), [('nil', lambda: ''), ('string', lambda: escape_html(val)), ('number', lambda: sx_str(val)), ('boolean', lambda: ('true' if sx_truthy(val) else 'false')), ('list', lambda: render_list_to_html(val, env)), ('raw-html', lambda: raw_html_content(val)), (None, lambda: escape_html(sx_str(val)))])
|
||||||
|
|
||||||
# RENDER_HTML_FORMS
|
# RENDER_HTML_FORMS
|
||||||
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defmacro', 'defstyle', 'defkeyframes', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
|
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
|
||||||
|
|
||||||
# render-html-form?
|
# render-html-form?
|
||||||
is_render_html_form = lambda name: contains_p(RENDER_HTML_FORMS, name)
|
is_render_html_form = lambda name: contains_p(RENDER_HTML_FORMS, name)
|
||||||
|
|||||||
@@ -1,782 +0,0 @@
|
|||||||
"""
|
|
||||||
Style dictionary — maps keyword atoms to CSS declarations.
|
|
||||||
|
|
||||||
Pure data. Each key is a Tailwind-compatible class name (used as an sx keyword
|
|
||||||
atom in ``(css :flex :gap-4 :p-2)``), and each value is the CSS declaration(s)
|
|
||||||
that class produces. Declarations are self-contained — no ``--tw-*`` custom
|
|
||||||
properties needed.
|
|
||||||
|
|
||||||
Generated from the codebase's tw.css via ``css_registry.py`` then simplified
|
|
||||||
to remove Tailwind v3 variable indirection.
|
|
||||||
|
|
||||||
Used by:
|
|
||||||
- ``style_resolver.py`` (server) — resolves ``(css ...)`` to StyleValue
|
|
||||||
- ``sx.js`` (client) — same resolution, cached in localStorage
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
# Base atoms — keyword → CSS declarations
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
#
|
|
||||||
# ~466 atoms covering all utilities used across the codebase.
|
|
||||||
# Variants (hover:*, sm:*, focus:*, etc.) are NOT stored here — the
|
|
||||||
# resolver splits "hover:bg-sky-200" into variant="hover" + atom="bg-sky-200"
|
|
||||||
# and wraps the declaration in the appropriate pseudo/media rule.
|
|
||||||
|
|
||||||
STYLE_ATOMS: dict[str, str] = {
|
|
||||||
# ── Display ──────────────────────────────────────────────────────────
|
|
||||||
"block": "display:block",
|
|
||||||
"inline-block": "display:inline-block",
|
|
||||||
"inline": "display:inline",
|
|
||||||
"flex": "display:flex",
|
|
||||||
"inline-flex": "display:inline-flex",
|
|
||||||
"table": "display:table",
|
|
||||||
"table-row": "display:table-row",
|
|
||||||
"grid": "display:grid",
|
|
||||||
"contents": "display:contents",
|
|
||||||
"hidden": "display:none",
|
|
||||||
|
|
||||||
# ── Position ─────────────────────────────────────────────────────────
|
|
||||||
"static": "position:static",
|
|
||||||
"fixed": "position:fixed",
|
|
||||||
"absolute": "position:absolute",
|
|
||||||
"relative": "position:relative",
|
|
||||||
"inset-0": "inset:0",
|
|
||||||
"top-0": "top:0",
|
|
||||||
"top-1/2": "top:50%",
|
|
||||||
"top-2": "top:.5rem",
|
|
||||||
"top-20": "top:5rem",
|
|
||||||
"top-[8px]": "top:8px",
|
|
||||||
"top-full": "top:100%",
|
|
||||||
"right-2": "right:.5rem",
|
|
||||||
"right-[8px]": "right:8px",
|
|
||||||
"bottom-full": "bottom:100%",
|
|
||||||
"left-1/2": "left:50%",
|
|
||||||
"left-2": "left:.5rem",
|
|
||||||
"-right-2": "right:-.5rem",
|
|
||||||
"-right-3": "right:-.75rem",
|
|
||||||
"-top-1.5": "top:-.375rem",
|
|
||||||
"-top-2": "top:-.5rem",
|
|
||||||
|
|
||||||
# ── Z-Index ──────────────────────────────────────────────────────────
|
|
||||||
"z-10": "z-index:10",
|
|
||||||
"z-40": "z-index:40",
|
|
||||||
"z-50": "z-index:50",
|
|
||||||
|
|
||||||
# ── Grid ─────────────────────────────────────────────────────────────
|
|
||||||
"grid-cols-1": "grid-template-columns:repeat(1,minmax(0,1fr))",
|
|
||||||
"grid-cols-2": "grid-template-columns:repeat(2,minmax(0,1fr))",
|
|
||||||
"grid-cols-3": "grid-template-columns:repeat(3,minmax(0,1fr))",
|
|
||||||
"grid-cols-4": "grid-template-columns:repeat(4,minmax(0,1fr))",
|
|
||||||
"grid-cols-5": "grid-template-columns:repeat(5,minmax(0,1fr))",
|
|
||||||
"grid-cols-6": "grid-template-columns:repeat(6,minmax(0,1fr))",
|
|
||||||
"grid-cols-7": "grid-template-columns:repeat(7,minmax(0,1fr))",
|
|
||||||
"grid-cols-12": "grid-template-columns:repeat(12,minmax(0,1fr))",
|
|
||||||
"col-span-2": "grid-column:span 2/span 2",
|
|
||||||
"col-span-3": "grid-column:span 3/span 3",
|
|
||||||
"col-span-4": "grid-column:span 4/span 4",
|
|
||||||
"col-span-5": "grid-column:span 5/span 5",
|
|
||||||
"col-span-12": "grid-column:span 12/span 12",
|
|
||||||
"col-span-full": "grid-column:1/-1",
|
|
||||||
|
|
||||||
# ── Flexbox ──────────────────────────────────────────────────────────
|
|
||||||
"flex-row": "flex-direction:row",
|
|
||||||
"flex-col": "flex-direction:column",
|
|
||||||
"flex-wrap": "flex-wrap:wrap",
|
|
||||||
"flex-0": "flex:0",
|
|
||||||
"flex-1": "flex:1 1 0%",
|
|
||||||
"flex-shrink-0": "flex-shrink:0",
|
|
||||||
"shrink-0": "flex-shrink:0",
|
|
||||||
"flex-shrink": "flex-shrink:1",
|
|
||||||
|
|
||||||
# ── Alignment ────────────────────────────────────────────────────────
|
|
||||||
"items-start": "align-items:flex-start",
|
|
||||||
"items-end": "align-items:flex-end",
|
|
||||||
"items-center": "align-items:center",
|
|
||||||
"items-baseline": "align-items:baseline",
|
|
||||||
"justify-start": "justify-content:flex-start",
|
|
||||||
"justify-end": "justify-content:flex-end",
|
|
||||||
"justify-center": "justify-content:center",
|
|
||||||
"justify-between": "justify-content:space-between",
|
|
||||||
"self-start": "align-self:flex-start",
|
|
||||||
"self-center": "align-self:center",
|
|
||||||
"place-items-center": "place-items:center",
|
|
||||||
|
|
||||||
# ── Gap ───────────────────────────────────────────────────────────────
|
|
||||||
"gap-px": "gap:1px",
|
|
||||||
"gap-0.5": "gap:.125rem",
|
|
||||||
"gap-1": "gap:.25rem",
|
|
||||||
"gap-1.5": "gap:.375rem",
|
|
||||||
"gap-2": "gap:.5rem",
|
|
||||||
"gap-3": "gap:.75rem",
|
|
||||||
"gap-4": "gap:1rem",
|
|
||||||
"gap-5": "gap:1.25rem",
|
|
||||||
"gap-6": "gap:1.5rem",
|
|
||||||
"gap-8": "gap:2rem",
|
|
||||||
"gap-[4px]": "gap:4px",
|
|
||||||
"gap-[8px]": "gap:8px",
|
|
||||||
"gap-[16px]": "gap:16px",
|
|
||||||
"gap-x-3": "column-gap:.75rem",
|
|
||||||
"gap-y-1": "row-gap:.25rem",
|
|
||||||
|
|
||||||
# ── Margin ───────────────────────────────────────────────────────────
|
|
||||||
"m-0": "margin:0",
|
|
||||||
"m-2": "margin:.5rem",
|
|
||||||
"mx-1": "margin-left:.25rem;margin-right:.25rem",
|
|
||||||
"mx-2": "margin-left:.5rem;margin-right:.5rem",
|
|
||||||
"mx-4": "margin-left:1rem;margin-right:1rem",
|
|
||||||
"mx-auto": "margin-left:auto;margin-right:auto",
|
|
||||||
"my-3": "margin-top:.75rem;margin-bottom:.75rem",
|
|
||||||
"-mb-px": "margin-bottom:-1px",
|
|
||||||
"mb-1": "margin-bottom:.25rem",
|
|
||||||
"mb-2": "margin-bottom:.5rem",
|
|
||||||
"mb-3": "margin-bottom:.75rem",
|
|
||||||
"mb-4": "margin-bottom:1rem",
|
|
||||||
"mb-6": "margin-bottom:1.5rem",
|
|
||||||
"mb-8": "margin-bottom:2rem",
|
|
||||||
"mb-12": "margin-bottom:3rem",
|
|
||||||
"mb-[8px]": "margin-bottom:8px",
|
|
||||||
"mb-[24px]": "margin-bottom:24px",
|
|
||||||
"ml-1": "margin-left:.25rem",
|
|
||||||
"ml-2": "margin-left:.5rem",
|
|
||||||
"ml-4": "margin-left:1rem",
|
|
||||||
"ml-auto": "margin-left:auto",
|
|
||||||
"mr-1": "margin-right:.25rem",
|
|
||||||
"mr-2": "margin-right:.5rem",
|
|
||||||
"mr-3": "margin-right:.75rem",
|
|
||||||
"mt-0.5": "margin-top:.125rem",
|
|
||||||
"mt-1": "margin-top:.25rem",
|
|
||||||
"mt-2": "margin-top:.5rem",
|
|
||||||
"mt-3": "margin-top:.75rem",
|
|
||||||
"mt-4": "margin-top:1rem",
|
|
||||||
"mt-5": "margin-top:1.25rem",
|
|
||||||
"mt-6": "margin-top:1.5rem",
|
|
||||||
"mt-8": "margin-top:2rem",
|
|
||||||
"mt-[8px]": "margin-top:8px",
|
|
||||||
"mt-[16px]": "margin-top:16px",
|
|
||||||
"mt-[32px]": "margin-top:32px",
|
|
||||||
|
|
||||||
# ── Padding ──────────────────────────────────────────────────────────
|
|
||||||
"p-0": "padding:0",
|
|
||||||
"p-1": "padding:.25rem",
|
|
||||||
"p-1.5": "padding:.375rem",
|
|
||||||
"p-2": "padding:.5rem",
|
|
||||||
"p-3": "padding:.75rem",
|
|
||||||
"p-4": "padding:1rem",
|
|
||||||
"p-5": "padding:1.25rem",
|
|
||||||
"p-6": "padding:1.5rem",
|
|
||||||
"p-8": "padding:2rem",
|
|
||||||
"px-1": "padding-left:.25rem;padding-right:.25rem",
|
|
||||||
"px-1.5": "padding-left:.375rem;padding-right:.375rem",
|
|
||||||
"px-2": "padding-left:.5rem;padding-right:.5rem",
|
|
||||||
"px-2.5": "padding-left:.625rem;padding-right:.625rem",
|
|
||||||
"px-3": "padding-left:.75rem;padding-right:.75rem",
|
|
||||||
"px-4": "padding-left:1rem;padding-right:1rem",
|
|
||||||
"px-6": "padding-left:1.5rem;padding-right:1.5rem",
|
|
||||||
"px-[8px]": "padding-left:8px;padding-right:8px",
|
|
||||||
"px-[12px]": "padding-left:12px;padding-right:12px",
|
|
||||||
"px-[16px]": "padding-left:16px;padding-right:16px",
|
|
||||||
"px-[20px]": "padding-left:20px;padding-right:20px",
|
|
||||||
"py-0.5": "padding-top:.125rem;padding-bottom:.125rem",
|
|
||||||
"py-1": "padding-top:.25rem;padding-bottom:.25rem",
|
|
||||||
"py-1.5": "padding-top:.375rem;padding-bottom:.375rem",
|
|
||||||
"py-2": "padding-top:.5rem;padding-bottom:.5rem",
|
|
||||||
"py-3": "padding-top:.75rem;padding-bottom:.75rem",
|
|
||||||
"py-4": "padding-top:1rem;padding-bottom:1rem",
|
|
||||||
"py-6": "padding-top:1.5rem;padding-bottom:1.5rem",
|
|
||||||
"py-8": "padding-top:2rem;padding-bottom:2rem",
|
|
||||||
"py-12": "padding-top:3rem;padding-bottom:3rem",
|
|
||||||
"py-16": "padding-top:4rem;padding-bottom:4rem",
|
|
||||||
"py-[6px]": "padding-top:6px;padding-bottom:6px",
|
|
||||||
"py-[12px]": "padding-top:12px;padding-bottom:12px",
|
|
||||||
"pb-1": "padding-bottom:.25rem",
|
|
||||||
"pb-2": "padding-bottom:.5rem",
|
|
||||||
"pb-3": "padding-bottom:.75rem",
|
|
||||||
"pb-4": "padding-bottom:1rem",
|
|
||||||
"pb-6": "padding-bottom:1.5rem",
|
|
||||||
"pb-8": "padding-bottom:2rem",
|
|
||||||
"pb-[48px]": "padding-bottom:48px",
|
|
||||||
"pl-2": "padding-left:.5rem",
|
|
||||||
"pl-3": "padding-left:.75rem",
|
|
||||||
"pl-5": "padding-left:1.25rem",
|
|
||||||
"pl-6": "padding-left:1.5rem",
|
|
||||||
"pr-1": "padding-right:.25rem",
|
|
||||||
"pr-2": "padding-right:.5rem",
|
|
||||||
"pr-4": "padding-right:1rem",
|
|
||||||
"pt-2": "padding-top:.5rem",
|
|
||||||
"pt-3": "padding-top:.75rem",
|
|
||||||
"pt-4": "padding-top:1rem",
|
|
||||||
"pt-[16px]": "padding-top:16px",
|
|
||||||
|
|
||||||
# ── Width ────────────────────────────────────────────────────────────
|
|
||||||
"w-1": "width:.25rem",
|
|
||||||
"w-2": "width:.5rem",
|
|
||||||
"w-4": "width:1rem",
|
|
||||||
"w-5": "width:1.25rem",
|
|
||||||
"w-6": "width:1.5rem",
|
|
||||||
"w-8": "width:2rem",
|
|
||||||
"w-10": "width:2.5rem",
|
|
||||||
"w-11": "width:2.75rem",
|
|
||||||
"w-12": "width:3rem",
|
|
||||||
"w-14": "width:3.5rem",
|
|
||||||
"w-16": "width:4rem",
|
|
||||||
"w-20": "width:5rem",
|
|
||||||
"w-24": "width:6rem",
|
|
||||||
"w-28": "width:7rem",
|
|
||||||
"w-32": "width:8rem",
|
|
||||||
"w-40": "width:10rem",
|
|
||||||
"w-48": "width:12rem",
|
|
||||||
"w-56": "width:14rem",
|
|
||||||
"w-1/2": "width:50%",
|
|
||||||
"w-1/3": "width:33.333333%",
|
|
||||||
"w-1/4": "width:25%",
|
|
||||||
"w-1/6": "width:16.666667%",
|
|
||||||
"w-2/6": "width:33.333333%",
|
|
||||||
"w-3/4": "width:75%",
|
|
||||||
"w-full": "width:100%",
|
|
||||||
"w-auto": "width:auto",
|
|
||||||
"w-[1em]": "width:1em",
|
|
||||||
"w-[32px]": "width:32px",
|
|
||||||
|
|
||||||
# ── Height ───────────────────────────────────────────────────────────
|
|
||||||
"h-2": "height:.5rem",
|
|
||||||
"h-4": "height:1rem",
|
|
||||||
"h-5": "height:1.25rem",
|
|
||||||
"h-6": "height:1.5rem",
|
|
||||||
"h-8": "height:2rem",
|
|
||||||
"h-10": "height:2.5rem",
|
|
||||||
"h-12": "height:3rem",
|
|
||||||
"h-14": "height:3.5rem",
|
|
||||||
"h-14": "height:3.5rem",
|
|
||||||
"h-16": "height:4rem",
|
|
||||||
"h-24": "height:6rem",
|
|
||||||
"h-28": "height:7rem",
|
|
||||||
"h-48": "height:12rem",
|
|
||||||
"h-64": "height:16rem",
|
|
||||||
"h-full": "height:100%",
|
|
||||||
"h-[1em]": "height:1em",
|
|
||||||
"h-[30vh]": "height:30vh",
|
|
||||||
"h-[32px]": "height:32px",
|
|
||||||
"h-[60vh]": "height:60vh",
|
|
||||||
|
|
||||||
# ── Min/Max Dimensions ───────────────────────────────────────────────
|
|
||||||
"min-w-0": "min-width:0",
|
|
||||||
"min-w-full": "min-width:100%",
|
|
||||||
"min-w-[1.25rem]": "min-width:1.25rem",
|
|
||||||
"min-w-[180px]": "min-width:180px",
|
|
||||||
"min-h-0": "min-height:0",
|
|
||||||
"min-h-20": "min-height:5rem",
|
|
||||||
"min-h-[3rem]": "min-height:3rem",
|
|
||||||
"min-h-[50vh]": "min-height:50vh",
|
|
||||||
"max-w-xs": "max-width:20rem",
|
|
||||||
"max-w-md": "max-width:28rem",
|
|
||||||
"max-w-lg": "max-width:32rem",
|
|
||||||
"max-w-2xl": "max-width:42rem",
|
|
||||||
"max-w-3xl": "max-width:48rem",
|
|
||||||
"max-w-4xl": "max-width:56rem",
|
|
||||||
"max-w-full": "max-width:100%",
|
|
||||||
"max-w-0": "max-width:0",
|
|
||||||
"max-w-none": "max-width:none",
|
|
||||||
"max-w-screen-2xl": "max-width:1536px",
|
|
||||||
"max-w-[360px]": "max-width:360px",
|
|
||||||
"max-w-[768px]": "max-width:768px",
|
|
||||||
"max-w-[640px]": "max-width:640px",
|
|
||||||
"max-h-32": "max-height:8rem",
|
|
||||||
"max-h-64": "max-height:16rem",
|
|
||||||
"max-h-72": "max-height:18rem",
|
|
||||||
"max-h-96": "max-height:24rem",
|
|
||||||
"max-h-none": "max-height:none",
|
|
||||||
"max-h-[448px]": "max-height:448px",
|
|
||||||
"max-h-[50vh]": "max-height:50vh",
|
|
||||||
|
|
||||||
# ── Typography ───────────────────────────────────────────────────────
|
|
||||||
"text-xs": "font-size:.75rem;line-height:1rem",
|
|
||||||
"text-sm": "font-size:.875rem;line-height:1.25rem",
|
|
||||||
"text-base": "font-size:1rem;line-height:1.5rem",
|
|
||||||
"text-md": "font-size:1rem;line-height:1.5rem", # alias for text-base
|
|
||||||
"text-lg": "font-size:1.125rem;line-height:1.75rem",
|
|
||||||
"text-xl": "font-size:1.25rem;line-height:1.75rem",
|
|
||||||
"text-2xl": "font-size:1.5rem;line-height:2rem",
|
|
||||||
"text-3xl": "font-size:1.875rem;line-height:2.25rem",
|
|
||||||
"text-4xl": "font-size:2.25rem;line-height:2.5rem",
|
|
||||||
"text-5xl": "font-size:3rem;line-height:1",
|
|
||||||
"text-6xl": "font-size:3.75rem;line-height:1",
|
|
||||||
"text-8xl": "font-size:6rem;line-height:1",
|
|
||||||
"text-[8px]": "font-size:8px",
|
|
||||||
"text-[9px]": "font-size:9px",
|
|
||||||
"text-[10px]": "font-size:10px",
|
|
||||||
"text-[11px]": "font-size:11px",
|
|
||||||
"text-[13px]": "font-size:13px",
|
|
||||||
"text-[14px]": "font-size:14px",
|
|
||||||
"text-[16px]": "font-size:16px",
|
|
||||||
"text-[18px]": "font-size:18px",
|
|
||||||
"text-[36px]": "font-size:36px",
|
|
||||||
"text-[40px]": "font-size:40px",
|
|
||||||
"text-[0.6rem]": "font-size:.6rem",
|
|
||||||
"text-[0.65rem]": "font-size:.65rem",
|
|
||||||
"text-[0.7rem]": "font-size:.7rem",
|
|
||||||
"font-normal": "font-weight:400",
|
|
||||||
"font-medium": "font-weight:500",
|
|
||||||
"font-semibold": "font-weight:600",
|
|
||||||
"font-bold": "font-weight:700",
|
|
||||||
"font-mono": "font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
|
|
||||||
"italic": "font-style:italic",
|
|
||||||
"uppercase": "text-transform:uppercase",
|
|
||||||
"capitalize": "text-transform:capitalize",
|
|
||||||
"tabular-nums": "font-variant-numeric:tabular-nums",
|
|
||||||
"leading-none": "line-height:1",
|
|
||||||
"leading-tight": "line-height:1.25",
|
|
||||||
"leading-snug": "line-height:1.375",
|
|
||||||
"leading-relaxed": "line-height:1.625",
|
|
||||||
"tracking-tight": "letter-spacing:-.025em",
|
|
||||||
"tracking-wide": "letter-spacing:.025em",
|
|
||||||
"tracking-widest": "letter-spacing:.1em",
|
|
||||||
"text-left": "text-align:left",
|
|
||||||
"text-center": "text-align:center",
|
|
||||||
"text-right": "text-align:right",
|
|
||||||
"align-top": "vertical-align:top",
|
|
||||||
|
|
||||||
# ── Text Colors ──────────────────────────────────────────────────────
|
|
||||||
"text-white": "color:rgb(255 255 255)",
|
|
||||||
"text-white/80": "color:rgba(255,255,255,.8)",
|
|
||||||
"text-black": "color:rgb(0 0 0)",
|
|
||||||
"text-stone-300": "color:rgb(214 211 209)",
|
|
||||||
"text-stone-400": "color:rgb(168 162 158)",
|
|
||||||
"text-stone-500": "color:rgb(120 113 108)",
|
|
||||||
"text-stone-600": "color:rgb(87 83 78)",
|
|
||||||
"text-stone-700": "color:rgb(68 64 60)",
|
|
||||||
"text-stone-800": "color:rgb(41 37 36)",
|
|
||||||
"text-stone-900": "color:rgb(28 25 23)",
|
|
||||||
"text-slate-400": "color:rgb(148 163 184)",
|
|
||||||
"text-gray-500": "color:rgb(107 114 128)",
|
|
||||||
"text-gray-600": "color:rgb(75 85 99)",
|
|
||||||
"text-red-500": "color:rgb(239 68 68)",
|
|
||||||
"text-red-600": "color:rgb(220 38 38)",
|
|
||||||
"text-red-700": "color:rgb(185 28 28)",
|
|
||||||
"text-red-800": "color:rgb(153 27 27)",
|
|
||||||
"text-rose-500": "color:rgb(244 63 94)",
|
|
||||||
"text-rose-600": "color:rgb(225 29 72)",
|
|
||||||
"text-rose-700": "color:rgb(190 18 60)",
|
|
||||||
"text-rose-800": "color:rgb(159 18 57)",
|
|
||||||
"text-rose-800/80": "color:rgba(159,18,57,.8)",
|
|
||||||
"text-rose-900": "color:rgb(136 19 55)",
|
|
||||||
"text-orange-600": "color:rgb(234 88 12)",
|
|
||||||
"text-amber-500": "color:rgb(245 158 11)",
|
|
||||||
"text-amber-600": "color:rgb(217 119 6)",
|
|
||||||
"text-amber-700": "color:rgb(180 83 9)",
|
|
||||||
"text-amber-800": "color:rgb(146 64 14)",
|
|
||||||
"text-yellow-700": "color:rgb(161 98 7)",
|
|
||||||
"text-green-600": "color:rgb(22 163 74)",
|
|
||||||
"text-green-800": "color:rgb(22 101 52)",
|
|
||||||
"text-green-900": "color:rgb(20 83 45)",
|
|
||||||
"text-neutral-400": "color:rgb(163 163 163)",
|
|
||||||
"text-neutral-500": "color:rgb(115 115 115)",
|
|
||||||
"text-neutral-600": "color:rgb(82 82 82)",
|
|
||||||
"text-emerald-500": "color:rgb(16 185 129)",
|
|
||||||
"text-emerald-600": "color:rgb(5 150 105)",
|
|
||||||
"text-emerald-700": "color:rgb(4 120 87)",
|
|
||||||
"text-emerald-800": "color:rgb(6 95 70)",
|
|
||||||
"text-emerald-900": "color:rgb(6 78 59)",
|
|
||||||
"text-sky-600": "color:rgb(2 132 199)",
|
|
||||||
"text-sky-700": "color:rgb(3 105 161)",
|
|
||||||
"text-sky-800": "color:rgb(7 89 133)",
|
|
||||||
"text-blue-500": "color:rgb(59 130 246)",
|
|
||||||
"text-blue-600": "color:rgb(37 99 235)",
|
|
||||||
"text-blue-700": "color:rgb(29 78 216)",
|
|
||||||
"text-blue-800": "color:rgb(30 64 175)",
|
|
||||||
"text-purple-600": "color:rgb(147 51 234)",
|
|
||||||
"text-violet-600": "color:rgb(124 58 237)",
|
|
||||||
"text-violet-700": "color:rgb(109 40 217)",
|
|
||||||
"text-violet-800": "color:rgb(91 33 182)",
|
|
||||||
"text-violet-900": "color:rgb(76 29 149)",
|
|
||||||
|
|
||||||
# ── Background Colors ────────────────────────────────────────────────
|
|
||||||
"bg-transparent": "background-color:transparent",
|
|
||||||
"bg-white": "background-color:rgb(255 255 255)",
|
|
||||||
"bg-white/60": "background-color:rgba(255,255,255,.6)",
|
|
||||||
"bg-white/70": "background-color:rgba(255,255,255,.7)",
|
|
||||||
"bg-white/80": "background-color:rgba(255,255,255,.8)",
|
|
||||||
"bg-white/90": "background-color:rgba(255,255,255,.9)",
|
|
||||||
"bg-black": "background-color:rgb(0 0 0)",
|
|
||||||
"bg-black/50": "background-color:rgba(0,0,0,.5)",
|
|
||||||
"bg-stone-50": "background-color:rgb(250 250 249)",
|
|
||||||
"bg-stone-100": "background-color:rgb(245 245 244)",
|
|
||||||
"bg-stone-200": "background-color:rgb(231 229 228)",
|
|
||||||
"bg-stone-300": "background-color:rgb(214 211 209)",
|
|
||||||
"bg-stone-400": "background-color:rgb(168 162 158)",
|
|
||||||
"bg-stone-500": "background-color:rgb(120 113 108)",
|
|
||||||
"bg-stone-600": "background-color:rgb(87 83 78)",
|
|
||||||
"bg-stone-700": "background-color:rgb(68 64 60)",
|
|
||||||
"bg-stone-800": "background-color:rgb(41 37 36)",
|
|
||||||
"bg-stone-900": "background-color:rgb(28 25 23)",
|
|
||||||
"bg-slate-100": "background-color:rgb(241 245 249)",
|
|
||||||
"bg-slate-200": "background-color:rgb(226 232 240)",
|
|
||||||
"bg-gray-100": "background-color:rgb(243 244 246)",
|
|
||||||
"bg-red-50": "background-color:rgb(254 242 242)",
|
|
||||||
"bg-red-100": "background-color:rgb(254 226 226)",
|
|
||||||
"bg-red-200": "background-color:rgb(254 202 202)",
|
|
||||||
"bg-red-500": "background-color:rgb(239 68 68)",
|
|
||||||
"bg-red-600": "background-color:rgb(220 38 38)",
|
|
||||||
"bg-rose-50": "background-color:rgb(255 241 242)",
|
|
||||||
"bg-rose-50/80": "background-color:rgba(255,241,242,.8)",
|
|
||||||
"bg-orange-100": "background-color:rgb(255 237 213)",
|
|
||||||
"bg-amber-50": "background-color:rgb(255 251 235)",
|
|
||||||
"bg-amber-50/60": "background-color:rgba(255,251,235,.6)",
|
|
||||||
"bg-amber-100": "background-color:rgb(254 243 199)",
|
|
||||||
"bg-amber-500": "background-color:rgb(245 158 11)",
|
|
||||||
"bg-amber-600": "background-color:rgb(217 119 6)",
|
|
||||||
"bg-yellow-50": "background-color:rgb(254 252 232)",
|
|
||||||
"bg-yellow-100": "background-color:rgb(254 249 195)",
|
|
||||||
"bg-yellow-200": "background-color:rgb(254 240 138)",
|
|
||||||
"bg-yellow-300": "background-color:rgb(253 224 71)",
|
|
||||||
"bg-green-50": "background-color:rgb(240 253 244)",
|
|
||||||
"bg-green-100": "background-color:rgb(220 252 231)",
|
|
||||||
"bg-green-200": "background-color:rgb(187 247 208)",
|
|
||||||
"bg-neutral-50/70": "background-color:rgba(250,250,250,.7)",
|
|
||||||
"bg-black/70": "background-color:rgba(0,0,0,.7)",
|
|
||||||
"bg-emerald-50": "background-color:rgb(236 253 245)",
|
|
||||||
"bg-emerald-50/80": "background-color:rgba(236,253,245,.8)",
|
|
||||||
"bg-emerald-100": "background-color:rgb(209 250 229)",
|
|
||||||
"bg-emerald-200": "background-color:rgb(167 243 208)",
|
|
||||||
"bg-emerald-500": "background-color:rgb(16 185 129)",
|
|
||||||
"bg-emerald-600": "background-color:rgb(5 150 105)",
|
|
||||||
"bg-sky-100": "background-color:rgb(224 242 254)",
|
|
||||||
"bg-sky-200": "background-color:rgb(186 230 253)",
|
|
||||||
"bg-sky-300": "background-color:rgb(125 211 252)",
|
|
||||||
"bg-sky-400": "background-color:rgb(56 189 248)",
|
|
||||||
"bg-sky-500": "background-color:rgb(14 165 233)",
|
|
||||||
"bg-blue-50": "background-color:rgb(239 246 255)",
|
|
||||||
"bg-blue-100": "background-color:rgb(219 234 254)",
|
|
||||||
"bg-blue-600": "background-color:rgb(37 99 235)",
|
|
||||||
"bg-purple-600": "background-color:rgb(147 51 234)",
|
|
||||||
"bg-violet-50": "background-color:rgb(245 243 255)",
|
|
||||||
"bg-violet-100": "background-color:rgb(237 233 254)",
|
|
||||||
"bg-violet-200": "background-color:rgb(221 214 254)",
|
|
||||||
"bg-violet-300": "background-color:rgb(196 181 253)",
|
|
||||||
"bg-violet-400": "background-color:rgb(167 139 250)",
|
|
||||||
"bg-violet-500": "background-color:rgb(139 92 246)",
|
|
||||||
"bg-violet-600": "background-color:rgb(124 58 237)",
|
|
||||||
"bg-violet-700": "background-color:rgb(109 40 217)",
|
|
||||||
"bg-amber-200": "background-color:rgb(253 230 138)",
|
|
||||||
"bg-blue-700": "background-color:rgb(29 78 216)",
|
|
||||||
"bg-emerald-700": "background-color:rgb(4 120 87)",
|
|
||||||
"bg-purple-700": "background-color:rgb(126 34 206)",
|
|
||||||
"bg-stone-50/60": "background-color:rgba(250,250,249,.6)",
|
|
||||||
|
|
||||||
# ── Border ───────────────────────────────────────────────────────────
|
|
||||||
"border": "border-width:1px",
|
|
||||||
"border-2": "border-width:2px",
|
|
||||||
"border-4": "border-width:4px",
|
|
||||||
"border-t": "border-top-width:1px",
|
|
||||||
"border-t-0": "border-top-width:0",
|
|
||||||
"border-b": "border-bottom-width:1px",
|
|
||||||
"border-b-2": "border-bottom-width:2px",
|
|
||||||
"border-r": "border-right-width:1px",
|
|
||||||
"border-l": "border-left-width:1px",
|
|
||||||
"border-l-4": "border-left-width:4px",
|
|
||||||
"border-dashed": "border-style:dashed",
|
|
||||||
"border-none": "border-style:none",
|
|
||||||
"border-transparent": "border-color:transparent",
|
|
||||||
"border-white": "border-color:rgb(255 255 255)",
|
|
||||||
"border-white/30": "border-color:rgba(255,255,255,.3)",
|
|
||||||
"border-stone-100": "border-color:rgb(245 245 244)",
|
|
||||||
"border-stone-200": "border-color:rgb(231 229 228)",
|
|
||||||
"border-stone-300": "border-color:rgb(214 211 209)",
|
|
||||||
"border-stone-700": "border-color:rgb(68 64 60)",
|
|
||||||
"border-red-200": "border-color:rgb(254 202 202)",
|
|
||||||
"border-red-300": "border-color:rgb(252 165 165)",
|
|
||||||
"border-rose-200": "border-color:rgb(254 205 211)",
|
|
||||||
"border-rose-300": "border-color:rgb(253 164 175)",
|
|
||||||
"border-amber-200": "border-color:rgb(253 230 138)",
|
|
||||||
"border-amber-300": "border-color:rgb(252 211 77)",
|
|
||||||
"border-yellow-200": "border-color:rgb(254 240 138)",
|
|
||||||
"border-green-300": "border-color:rgb(134 239 172)",
|
|
||||||
"border-emerald-100": "border-color:rgb(209 250 229)",
|
|
||||||
"border-emerald-200": "border-color:rgb(167 243 208)",
|
|
||||||
"border-emerald-300": "border-color:rgb(110 231 183)",
|
|
||||||
"border-emerald-600": "border-color:rgb(5 150 105)",
|
|
||||||
"border-blue-200": "border-color:rgb(191 219 254)",
|
|
||||||
"border-blue-300": "border-color:rgb(147 197 253)",
|
|
||||||
"border-violet-200": "border-color:rgb(221 214 254)",
|
|
||||||
"border-violet-300": "border-color:rgb(196 181 253)",
|
|
||||||
"border-violet-400": "border-color:rgb(167 139 250)",
|
|
||||||
"border-neutral-200": "border-color:rgb(229 229 229)",
|
|
||||||
"border-red-400": "border-color:rgb(248 113 113)",
|
|
||||||
"border-stone-400": "border-color:rgb(168 162 158)",
|
|
||||||
"border-t-white": "border-top-color:rgb(255 255 255)",
|
|
||||||
"border-t-stone-600": "border-top-color:rgb(87 83 78)",
|
|
||||||
"border-l-stone-400": "border-left-color:rgb(168 162 158)",
|
|
||||||
|
|
||||||
# ── Border Radius ────────────────────────────────────────────────────
|
|
||||||
"rounded": "border-radius:.25rem",
|
|
||||||
"rounded-md": "border-radius:.375rem",
|
|
||||||
"rounded-lg": "border-radius:.5rem",
|
|
||||||
"rounded-xl": "border-radius:.75rem",
|
|
||||||
"rounded-2xl": "border-radius:1rem",
|
|
||||||
"rounded-full": "border-radius:9999px",
|
|
||||||
"rounded-t": "border-top-left-radius:.25rem;border-top-right-radius:.25rem",
|
|
||||||
"rounded-b": "border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem",
|
|
||||||
"rounded-[4px]": "border-radius:4px",
|
|
||||||
"rounded-[8px]": "border-radius:8px",
|
|
||||||
|
|
||||||
# ── Shadow ───────────────────────────────────────────────────────────
|
|
||||||
"shadow-sm": "box-shadow:0 1px 2px 0 rgba(0,0,0,.05)",
|
|
||||||
"shadow": "box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1)",
|
|
||||||
"shadow-md": "box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)",
|
|
||||||
"shadow-lg": "box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1)",
|
|
||||||
"shadow-xl": "box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1)",
|
|
||||||
|
|
||||||
# ── Opacity ──────────────────────────────────────────────────────────
|
|
||||||
"opacity-0": "opacity:0",
|
|
||||||
"opacity-40": "opacity:.4",
|
|
||||||
"opacity-50": "opacity:.5",
|
|
||||||
"opacity-90": "opacity:.9",
|
|
||||||
"opacity-100": "opacity:1",
|
|
||||||
|
|
||||||
# ── Ring / Outline ───────────────────────────────────────────────────
|
|
||||||
"outline-none": "outline:2px solid transparent;outline-offset:2px",
|
|
||||||
"ring-2": "box-shadow:0 0 0 2px var(--tw-ring-color,rgb(59 130 246))",
|
|
||||||
"ring-offset-2": "box-shadow:0 0 0 2px rgb(255 255 255),0 0 0 4px var(--tw-ring-color,rgb(59 130 246))",
|
|
||||||
"ring-stone-300": "--tw-ring-color:rgb(214 211 209)",
|
|
||||||
"ring-stone-500": "--tw-ring-color:rgb(120 113 108)",
|
|
||||||
"ring-violet-500": "--tw-ring-color:rgb(139 92 246)",
|
|
||||||
"ring-blue-500": "--tw-ring-color:rgb(59 130 246)",
|
|
||||||
"ring-green-500": "--tw-ring-color:rgb(22 163 74)",
|
|
||||||
"ring-purple-500": "--tw-ring-color:rgb(147 51 234)",
|
|
||||||
|
|
||||||
# ── Overflow ─────────────────────────────────────────────────────────
|
|
||||||
"overflow-hidden": "overflow:hidden",
|
|
||||||
"overflow-x-auto": "overflow-x:auto",
|
|
||||||
"overflow-y-auto": "overflow-y:auto",
|
|
||||||
"overflow-visible": "overflow:visible",
|
|
||||||
"overflow-y-visible": "overflow-y:visible",
|
|
||||||
"overscroll-contain": "overscroll-behavior:contain",
|
|
||||||
|
|
||||||
# ── Text Decoration ──────────────────────────────────────────────────
|
|
||||||
"underline": "text-decoration-line:underline",
|
|
||||||
"line-through": "text-decoration-line:line-through",
|
|
||||||
"no-underline": "text-decoration-line:none",
|
|
||||||
|
|
||||||
# ── Text Overflow ────────────────────────────────────────────────────
|
|
||||||
"truncate": "overflow:hidden;text-overflow:ellipsis;white-space:nowrap",
|
|
||||||
"line-clamp-2": "display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden",
|
|
||||||
"line-clamp-3": "display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden",
|
|
||||||
|
|
||||||
# ── Whitespace / Word Break ──────────────────────────────────────────
|
|
||||||
"whitespace-normal": "white-space:normal",
|
|
||||||
"whitespace-nowrap": "white-space:nowrap",
|
|
||||||
"whitespace-pre-line": "white-space:pre-line",
|
|
||||||
"whitespace-pre-wrap": "white-space:pre-wrap",
|
|
||||||
"break-words": "overflow-wrap:break-word",
|
|
||||||
"break-all": "word-break:break-all",
|
|
||||||
|
|
||||||
# ── Transform ────────────────────────────────────────────────────────
|
|
||||||
"rotate-180": "transform:rotate(180deg)",
|
|
||||||
"-translate-x-1/2": "transform:translateX(-50%)",
|
|
||||||
"-translate-y-1/2": "transform:translateY(-50%)",
|
|
||||||
|
|
||||||
# ── Transition ───────────────────────────────────────────────────────
|
|
||||||
"transition": "transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s",
|
|
||||||
"transition-all": "transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s",
|
|
||||||
"transition-colors": "transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s",
|
|
||||||
"transition-opacity": "transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s",
|
|
||||||
"transition-transform": "transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s",
|
|
||||||
"duration-75": "transition-duration:75ms",
|
|
||||||
"duration-100": "transition-duration:100ms",
|
|
||||||
"duration-150": "transition-duration:150ms",
|
|
||||||
"duration-200": "transition-duration:200ms",
|
|
||||||
"duration-300": "transition-duration:300ms",
|
|
||||||
"duration-500": "transition-duration:500ms",
|
|
||||||
"duration-700": "transition-duration:700ms",
|
|
||||||
|
|
||||||
# ── Animation ────────────────────────────────────────────────────────
|
|
||||||
"animate-spin": "animation:spin 1s linear infinite",
|
|
||||||
"animate-ping": "animation:ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
||||||
"animate-pulse": "animation:pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
||||||
"animate-bounce": "animation:bounce 1s infinite",
|
|
||||||
"animate-none": "animation:none",
|
|
||||||
|
|
||||||
# ── Aspect Ratio ─────────────────────────────────────────────────────
|
|
||||||
"aspect-square": "aspect-ratio:1/1",
|
|
||||||
"aspect-video": "aspect-ratio:16/9",
|
|
||||||
|
|
||||||
# ── Object Fit / Position ────────────────────────────────────────────
|
|
||||||
"object-contain": "object-fit:contain",
|
|
||||||
"object-cover": "object-fit:cover",
|
|
||||||
"object-center": "object-position:center",
|
|
||||||
"object-top": "object-position:top",
|
|
||||||
|
|
||||||
# ── Cursor ───────────────────────────────────────────────────────────
|
|
||||||
"cursor-pointer": "cursor:pointer",
|
|
||||||
"cursor-move": "cursor:move",
|
|
||||||
|
|
||||||
# ── User Select ──────────────────────────────────────────────────────
|
|
||||||
"select-none": "user-select:none",
|
|
||||||
"select-all": "user-select:all",
|
|
||||||
|
|
||||||
# ── Pointer Events ───────────────────────────────────────────────────
|
|
||||||
"pointer-events-none": "pointer-events:none",
|
|
||||||
|
|
||||||
# ── Resize ───────────────────────────────────────────────────────────
|
|
||||||
"resize": "resize:both",
|
|
||||||
"resize-none": "resize:none",
|
|
||||||
|
|
||||||
# ── Scroll Snap ──────────────────────────────────────────────────────
|
|
||||||
"snap-y": "scroll-snap-type:y mandatory",
|
|
||||||
"snap-start": "scroll-snap-align:start",
|
|
||||||
"snap-mandatory": "scroll-snap-type:y mandatory",
|
|
||||||
|
|
||||||
# ── List Style ───────────────────────────────────────────────────────
|
|
||||||
"list-disc": "list-style-type:disc",
|
|
||||||
"list-decimal": "list-style-type:decimal",
|
|
||||||
"list-inside": "list-style-position:inside",
|
|
||||||
|
|
||||||
# ── Table ────────────────────────────────────────────────────────────
|
|
||||||
"table-fixed": "table-layout:fixed",
|
|
||||||
|
|
||||||
# ── Backdrop ─────────────────────────────────────────────────────────
|
|
||||||
"backdrop-blur": "backdrop-filter:blur(8px)",
|
|
||||||
"backdrop-blur-sm": "backdrop-filter:blur(4px)",
|
|
||||||
"backdrop-blur-md": "backdrop-filter:blur(12px)",
|
|
||||||
|
|
||||||
# ── Filter ───────────────────────────────────────────────────────────
|
|
||||||
"saturate-0": "filter:saturate(0)",
|
|
||||||
|
|
||||||
# ── Space Between (child selector atoms) ─────────────────────────────
|
|
||||||
# These generate `.atom > :not(:first-child)` rules
|
|
||||||
"space-y-0": "margin-top:0",
|
|
||||||
"space-y-0.5": "margin-top:.125rem",
|
|
||||||
"space-y-1": "margin-top:.25rem",
|
|
||||||
"space-y-2": "margin-top:.5rem",
|
|
||||||
"space-y-3": "margin-top:.75rem",
|
|
||||||
"space-y-4": "margin-top:1rem",
|
|
||||||
"space-y-6": "margin-top:1.5rem",
|
|
||||||
"space-y-8": "margin-top:2rem",
|
|
||||||
"space-y-10": "margin-top:2.5rem",
|
|
||||||
"space-x-1": "margin-left:.25rem",
|
|
||||||
"space-x-2": "margin-left:.5rem",
|
|
||||||
|
|
||||||
# ── Divide (child selector atoms) ────────────────────────────────────
|
|
||||||
# These generate `.atom > :not(:first-child)` rules
|
|
||||||
"divide-y": "border-top-width:1px",
|
|
||||||
"divide-stone-100": "border-color:rgb(245 245 244)",
|
|
||||||
"divide-stone-200": "border-color:rgb(231 229 228)",
|
|
||||||
|
|
||||||
# ── Important modifiers ──────────────────────────────────────────────
|
|
||||||
"!bg-stone-500": "background-color:rgb(120 113 108)!important",
|
|
||||||
"!text-white": "color:rgb(255 255 255)!important",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Atoms that need a child selector: `.atom > :not(:first-child)` instead of `.atom`
|
|
||||||
CHILD_SELECTOR_ATOMS: frozenset[str] = frozenset({
|
|
||||||
k for k in STYLE_ATOMS
|
|
||||||
if k.startswith(("space-x-", "space-y-", "divide-y", "divide-x"))
|
|
||||||
and not k.startswith("divide-stone")
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
# Pseudo-class / pseudo-element variants
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
PSEUDO_VARIANTS: dict[str, str] = {
|
|
||||||
"hover": ":hover",
|
|
||||||
"focus": ":focus",
|
|
||||||
"focus-within": ":focus-within",
|
|
||||||
"focus-visible": ":focus-visible",
|
|
||||||
"active": ":active",
|
|
||||||
"disabled": ":disabled",
|
|
||||||
"first": ":first-child",
|
|
||||||
"last": ":last-child",
|
|
||||||
"odd": ":nth-child(odd)",
|
|
||||||
"even": ":nth-child(even)",
|
|
||||||
"empty": ":empty",
|
|
||||||
"open": "[open]",
|
|
||||||
"placeholder": "::placeholder",
|
|
||||||
"file": "::file-selector-button",
|
|
||||||
"aria-selected": "[aria-selected=true]",
|
|
||||||
"invalid": ":invalid",
|
|
||||||
"placeholder-shown": ":placeholder-shown",
|
|
||||||
"group-hover": ":is(.group:hover) &",
|
|
||||||
"group-open": ":is(.group[open]) &",
|
|
||||||
"group-open/cat": ":is(.group\\/cat[open]) &",
|
|
||||||
"group-open/filter": ":is(.group\\/filter[open]) &",
|
|
||||||
"group-open/root": ":is(.group\\/root[open]) &",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
# Responsive breakpoints
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
RESPONSIVE_BREAKPOINTS: dict[str, str] = {
|
|
||||||
"sm": "(min-width:640px)",
|
|
||||||
"md": "(min-width:768px)",
|
|
||||||
"lg": "(min-width:1024px)",
|
|
||||||
"xl": "(min-width:1280px)",
|
|
||||||
"2xl": "(min-width:1536px)",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
# Keyframes — built-in animation definitions
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
KEYFRAMES: dict[str, str] = {
|
|
||||||
"spin": "@keyframes spin{to{transform:rotate(360deg)}}",
|
|
||||||
"ping": "@keyframes ping{75%,100%{transform:scale(2);opacity:0}}",
|
|
||||||
"pulse": "@keyframes pulse{50%{opacity:.5}}",
|
|
||||||
"bounce": "@keyframes bounce{0%,100%{transform:translateY(-25%);animation-timing-function:cubic-bezier(0.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,0.2,1)}}",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
# Arbitrary value patterns — fallback when atom not in STYLE_ATOMS
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
#
|
|
||||||
# Each tuple is (regex_pattern, css_template).
|
|
||||||
# The regex captures value groups; the template uses {0}, {1}, etc.
|
|
||||||
|
|
||||||
ARBITRARY_PATTERNS: list[tuple[str, str]] = [
|
|
||||||
# Width / Height
|
|
||||||
(r"w-\[(.+)\]", "width:{0}"),
|
|
||||||
(r"h-\[(.+)\]", "height:{0}"),
|
|
||||||
(r"min-w-\[(.+)\]", "min-width:{0}"),
|
|
||||||
(r"min-h-\[(.+)\]", "min-height:{0}"),
|
|
||||||
(r"max-w-\[(.+)\]", "max-width:{0}"),
|
|
||||||
(r"max-h-\[(.+)\]", "max-height:{0}"),
|
|
||||||
# Spacing
|
|
||||||
(r"p-\[(.+)\]", "padding:{0}"),
|
|
||||||
(r"px-\[(.+)\]", "padding-left:{0};padding-right:{0}"),
|
|
||||||
(r"py-\[(.+)\]", "padding-top:{0};padding-bottom:{0}"),
|
|
||||||
(r"pt-\[(.+)\]", "padding-top:{0}"),
|
|
||||||
(r"pb-\[(.+)\]", "padding-bottom:{0}"),
|
|
||||||
(r"pl-\[(.+)\]", "padding-left:{0}"),
|
|
||||||
(r"pr-\[(.+)\]", "padding-right:{0}"),
|
|
||||||
(r"m-\[(.+)\]", "margin:{0}"),
|
|
||||||
(r"mx-\[(.+)\]", "margin-left:{0};margin-right:{0}"),
|
|
||||||
(r"my-\[(.+)\]", "margin-top:{0};margin-bottom:{0}"),
|
|
||||||
(r"mt-\[(.+)\]", "margin-top:{0}"),
|
|
||||||
(r"mb-\[(.+)\]", "margin-bottom:{0}"),
|
|
||||||
(r"ml-\[(.+)\]", "margin-left:{0}"),
|
|
||||||
(r"mr-\[(.+)\]", "margin-right:{0}"),
|
|
||||||
# Gap
|
|
||||||
(r"gap-\[(.+)\]", "gap:{0}"),
|
|
||||||
(r"gap-x-\[(.+)\]", "column-gap:{0}"),
|
|
||||||
(r"gap-y-\[(.+)\]", "row-gap:{0}"),
|
|
||||||
# Position
|
|
||||||
(r"top-\[(.+)\]", "top:{0}"),
|
|
||||||
(r"right-\[(.+)\]", "right:{0}"),
|
|
||||||
(r"bottom-\[(.+)\]", "bottom:{0}"),
|
|
||||||
(r"left-\[(.+)\]", "left:{0}"),
|
|
||||||
# Border radius
|
|
||||||
(r"rounded-\[(.+)\]", "border-radius:{0}"),
|
|
||||||
# Background / Text color
|
|
||||||
(r"bg-\[(.+)\]", "background-color:{0}"),
|
|
||||||
(r"text-\[(.+)\]", "font-size:{0}"),
|
|
||||||
# Grid
|
|
||||||
(r"grid-cols-\[(.+)\]", "grid-template-columns:{0}"),
|
|
||||||
(r"col-span-(\d+)", "grid-column:span {0}/span {0}"),
|
|
||||||
]
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
"""
|
|
||||||
Style resolver — ``(css :flex :gap-4 :hover:bg-sky-200)`` → StyleValue.
|
|
||||||
|
|
||||||
Resolves a tuple of atom strings into a ``StyleValue`` with:
|
|
||||||
- A content-addressed class name (``sx-{hash[:6]}``)
|
|
||||||
- Base CSS declarations
|
|
||||||
- Pseudo-class rules (hover, focus, etc.)
|
|
||||||
- Media-query rules (responsive breakpoints)
|
|
||||||
- Referenced @keyframes definitions
|
|
||||||
|
|
||||||
Resolution order per atom:
|
|
||||||
1. Dictionary lookup in ``STYLE_ATOMS``
|
|
||||||
2. Arbitrary value pattern match (``w-[347px]`` → ``width:347px``)
|
|
||||||
3. Ignored (unknown atoms are silently skipped)
|
|
||||||
|
|
||||||
Results are memoized by input tuple for zero-cost repeat calls.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import re
|
|
||||||
from functools import lru_cache
|
|
||||||
from typing import Sequence
|
|
||||||
|
|
||||||
from .style_dict import (
|
|
||||||
ARBITRARY_PATTERNS,
|
|
||||||
CHILD_SELECTOR_ATOMS,
|
|
||||||
KEYFRAMES,
|
|
||||||
PSEUDO_VARIANTS,
|
|
||||||
RESPONSIVE_BREAKPOINTS,
|
|
||||||
STYLE_ATOMS,
|
|
||||||
)
|
|
||||||
from .types import StyleValue
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Compiled arbitrary-value patterns
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
_COMPILED_PATTERNS: list[tuple[re.Pattern, str]] = [
|
|
||||||
(re.compile(f"^{pat}$"), tmpl)
|
|
||||||
for pat, tmpl in ARBITRARY_PATTERNS
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Public API
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def resolve_style(atoms: tuple[str, ...]) -> StyleValue:
|
|
||||||
"""Resolve a tuple of keyword atoms into a StyleValue.
|
|
||||||
|
|
||||||
Each atom is a Tailwind-compatible keyword (``flex``, ``gap-4``,
|
|
||||||
``hover:bg-sky-200``, ``sm:flex-row``, etc.). Both keywords
|
|
||||||
(without leading colon) and runtime strings are accepted.
|
|
||||||
"""
|
|
||||||
return _resolve_cached(atoms)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_styles(styles: Sequence[StyleValue]) -> StyleValue:
|
|
||||||
"""Merge multiple StyleValues into one.
|
|
||||||
|
|
||||||
Later declarations win for the same CSS property. Class name is
|
|
||||||
recomputed from the merged declarations.
|
|
||||||
"""
|
|
||||||
if len(styles) == 1:
|
|
||||||
return styles[0]
|
|
||||||
|
|
||||||
all_decls: list[str] = []
|
|
||||||
all_media: list[tuple[str, str]] = []
|
|
||||||
all_pseudo: list[tuple[str, str]] = []
|
|
||||||
all_kf: list[tuple[str, str]] = []
|
|
||||||
|
|
||||||
for sv in styles:
|
|
||||||
if sv.declarations:
|
|
||||||
all_decls.append(sv.declarations)
|
|
||||||
all_media.extend(sv.media_rules)
|
|
||||||
all_pseudo.extend(sv.pseudo_rules)
|
|
||||||
all_kf.extend(sv.keyframes)
|
|
||||||
|
|
||||||
merged_decls = ";".join(all_decls)
|
|
||||||
return _build_style_value(
|
|
||||||
merged_decls,
|
|
||||||
tuple(all_media),
|
|
||||||
tuple(all_pseudo),
|
|
||||||
tuple(dict(all_kf).items()), # dedupe keyframes by name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Internal resolution
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@lru_cache(maxsize=4096)
|
|
||||||
def _resolve_cached(atoms: tuple[str, ...]) -> StyleValue:
|
|
||||||
"""Memoized resolver."""
|
|
||||||
base_decls: list[str] = []
|
|
||||||
media_rules: list[tuple[str, str]] = [] # (query, decls)
|
|
||||||
pseudo_rules: list[tuple[str, str]] = [] # (selector_suffix, decls)
|
|
||||||
keyframes_needed: list[tuple[str, str]] = []
|
|
||||||
|
|
||||||
for atom in atoms:
|
|
||||||
if not atom:
|
|
||||||
continue
|
|
||||||
# Strip leading colon if keyword form (":flex" → "flex")
|
|
||||||
a = atom.lstrip(":")
|
|
||||||
|
|
||||||
# Split variant prefix(es): "hover:bg-sky-200" → ["hover", "bg-sky-200"]
|
|
||||||
# "sm:hover:bg-sky-200" → ["sm", "hover", "bg-sky-200"]
|
|
||||||
variant, base = _split_variant(a)
|
|
||||||
|
|
||||||
# Resolve the base atom to CSS declarations
|
|
||||||
decls = _resolve_atom(base)
|
|
||||||
if not decls:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check if this atom references a keyframe
|
|
||||||
_check_keyframes(base, keyframes_needed)
|
|
||||||
|
|
||||||
# Route to the appropriate bucket
|
|
||||||
if variant is None:
|
|
||||||
base_decls.append(decls)
|
|
||||||
elif variant in RESPONSIVE_BREAKPOINTS:
|
|
||||||
query = RESPONSIVE_BREAKPOINTS[variant]
|
|
||||||
media_rules.append((query, decls))
|
|
||||||
elif variant in PSEUDO_VARIANTS:
|
|
||||||
pseudo_sel = PSEUDO_VARIANTS[variant]
|
|
||||||
pseudo_rules.append((pseudo_sel, decls))
|
|
||||||
else:
|
|
||||||
# Compound variant: "sm:hover:..." → media + pseudo
|
|
||||||
parts = variant.split(":")
|
|
||||||
media_part = None
|
|
||||||
pseudo_part = None
|
|
||||||
for p in parts:
|
|
||||||
if p in RESPONSIVE_BREAKPOINTS:
|
|
||||||
media_part = RESPONSIVE_BREAKPOINTS[p]
|
|
||||||
elif p in PSEUDO_VARIANTS:
|
|
||||||
pseudo_part = PSEUDO_VARIANTS[p]
|
|
||||||
if media_part and pseudo_part:
|
|
||||||
# Both media and pseudo — store as pseudo within media
|
|
||||||
# For now, put in pseudo_rules with media annotation
|
|
||||||
pseudo_rules.append((pseudo_part, decls))
|
|
||||||
media_rules.append((media_part, decls))
|
|
||||||
elif media_part:
|
|
||||||
media_rules.append((media_part, decls))
|
|
||||||
elif pseudo_part:
|
|
||||||
pseudo_rules.append((pseudo_part, decls))
|
|
||||||
else:
|
|
||||||
# Unknown variant — treat as base
|
|
||||||
base_decls.append(decls)
|
|
||||||
|
|
||||||
return _build_style_value(
|
|
||||||
";".join(base_decls),
|
|
||||||
tuple(media_rules),
|
|
||||||
tuple(pseudo_rules),
|
|
||||||
tuple(keyframes_needed),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _split_variant(atom: str) -> tuple[str | None, str]:
|
|
||||||
"""Split a potentially variant-prefixed atom.
|
|
||||||
|
|
||||||
Returns (variant, base) where variant is None for non-prefixed atoms.
|
|
||||||
Examples:
|
|
||||||
"flex" → (None, "flex")
|
|
||||||
"hover:bg-sky-200" → ("hover", "bg-sky-200")
|
|
||||||
"sm:flex-row" → ("sm", "flex-row")
|
|
||||||
"sm:hover:bg-sky-200" → ("sm:hover", "bg-sky-200")
|
|
||||||
"""
|
|
||||||
# Check for responsive prefix first (always outermost)
|
|
||||||
for bp in RESPONSIVE_BREAKPOINTS:
|
|
||||||
prefix = bp + ":"
|
|
||||||
if atom.startswith(prefix):
|
|
||||||
rest = atom[len(prefix):]
|
|
||||||
# Check for nested pseudo variant
|
|
||||||
for pv in PSEUDO_VARIANTS:
|
|
||||||
inner_prefix = pv + ":"
|
|
||||||
if rest.startswith(inner_prefix):
|
|
||||||
return (bp + ":" + pv, rest[len(inner_prefix):])
|
|
||||||
return (bp, rest)
|
|
||||||
|
|
||||||
# Check for pseudo variant
|
|
||||||
for pv in PSEUDO_VARIANTS:
|
|
||||||
prefix = pv + ":"
|
|
||||||
if atom.startswith(prefix):
|
|
||||||
return (pv, atom[len(prefix):])
|
|
||||||
|
|
||||||
return (None, atom)
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_atom(atom: str) -> str | None:
|
|
||||||
"""Look up CSS declarations for a single base atom.
|
|
||||||
|
|
||||||
Returns None if the atom is unknown.
|
|
||||||
"""
|
|
||||||
# 1. Dictionary lookup
|
|
||||||
decls = STYLE_ATOMS.get(atom)
|
|
||||||
if decls is not None:
|
|
||||||
return decls
|
|
||||||
|
|
||||||
# 2. Dynamic keyframes: animate-{name} → animation-name:{name}
|
|
||||||
if atom.startswith("animate-"):
|
|
||||||
name = atom[len("animate-"):]
|
|
||||||
if name in KEYFRAMES:
|
|
||||||
return f"animation-name:{name}"
|
|
||||||
|
|
||||||
# 3. Arbitrary value pattern match
|
|
||||||
for pattern, template in _COMPILED_PATTERNS:
|
|
||||||
m = pattern.match(atom)
|
|
||||||
if m:
|
|
||||||
groups = m.groups()
|
|
||||||
result = template
|
|
||||||
for i, g in enumerate(groups):
|
|
||||||
result = result.replace(f"{{{i}}}", g)
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 4. Unknown atom — silently skip
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _check_keyframes(atom: str, kf_list: list[tuple[str, str]]) -> None:
|
|
||||||
"""If the atom references a built-in animation, add its @keyframes."""
|
|
||||||
if atom.startswith("animate-"):
|
|
||||||
name = atom[len("animate-"):]
|
|
||||||
if name in KEYFRAMES:
|
|
||||||
kf_list.append((name, KEYFRAMES[name]))
|
|
||||||
|
|
||||||
|
|
||||||
def _build_style_value(
|
|
||||||
declarations: str,
|
|
||||||
media_rules: tuple,
|
|
||||||
pseudo_rules: tuple,
|
|
||||||
keyframes: tuple,
|
|
||||||
) -> StyleValue:
|
|
||||||
"""Build a StyleValue with a content-addressed class name."""
|
|
||||||
# Build hash from all rules for deterministic class name
|
|
||||||
hash_input = declarations
|
|
||||||
for query, decls in media_rules:
|
|
||||||
hash_input += f"@{query}{{{decls}}}"
|
|
||||||
for sel, decls in pseudo_rules:
|
|
||||||
hash_input += f"{sel}{{{decls}}}"
|
|
||||||
for name, rule in keyframes:
|
|
||||||
hash_input += rule
|
|
||||||
|
|
||||||
h = hashlib.sha256(hash_input.encode()).hexdigest()[:6]
|
|
||||||
class_name = f"sx-{h}"
|
|
||||||
|
|
||||||
return StyleValue(
|
|
||||||
class_name=class_name,
|
|
||||||
declarations=declarations,
|
|
||||||
media_rules=media_rules,
|
|
||||||
pseudo_rules=pseudo_rules,
|
|
||||||
keyframes=keyframes,
|
|
||||||
)
|
|
||||||
@@ -314,30 +314,6 @@ class ActionDef:
|
|||||||
return f"<action:{self.name}({', '.join(self.params)})>"
|
return f"<action:{self.name}({', '.join(self.params)})>"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# StyleValue
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class StyleValue:
|
|
||||||
"""A resolved CSS style produced by ``(css :flex :gap-4 :hover:bg-sky-200)``.
|
|
||||||
|
|
||||||
Generated by the style resolver. The renderer emits ``class_name`` as a
|
|
||||||
CSS class and registers the CSS rule for on-demand delivery.
|
|
||||||
"""
|
|
||||||
class_name: str # "sx-a3f2c1"
|
|
||||||
declarations: str # "display:flex;gap:1rem"
|
|
||||||
media_rules: tuple = () # ((query, decls), ...)
|
|
||||||
pseudo_rules: tuple = () # ((selector, decls), ...)
|
|
||||||
keyframes: tuple = () # (("spin", "@keyframes spin{...}"), ...)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<StyleValue {self.class_name}>"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.class_name
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Continuation
|
# Continuation
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -379,4 +355,4 @@ class _ShiftSignal(BaseException):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# An s-expression value after evaluation
|
# An s-expression value after evaluation
|
||||||
SExp = int | float | str | bool | Symbol | Keyword | Lambda | Macro | Component | Continuation | HandlerDef | RelationDef | PageDef | QueryDef | ActionDef | StyleValue | list | dict | _Nil | None
|
SExp = int | float | str | bool | Symbol | Keyword | Lambda | Macro | Component | Continuation | HandlerDef | RelationDef | PageDef | QueryDef | ActionDef | list | dict | _Nil | None
|
||||||
|
|||||||
421
sx/sx/cssx.sx
Normal file
421
sx/sx/cssx.sx
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
;; CSSX — Styling as Components
|
||||||
|
;; Documentation for the CSSX approach: no parallel style infrastructure,
|
||||||
|
;; just defcomp components that decide how to style their children.
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Overview
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-overview-content ()
|
||||||
|
(~doc-page :title "CSSX Components"
|
||||||
|
|
||||||
|
(~doc-section :title "The Idea" :id "idea"
|
||||||
|
(p (strong "Styling is just components.") " A CSSX component is a regular "
|
||||||
|
(code "defcomp") " that decides how to style its children. It might apply "
|
||||||
|
"Tailwind classes, or hand-written CSS classes, or inline styles, or generate "
|
||||||
|
"rules at runtime. The implementation is the component's private business. "
|
||||||
|
"The consumer just calls " (code "(~btn :variant \"primary\" \"Submit\")") " and doesn't care.")
|
||||||
|
(p "Because it's " (code "defcomp") ", you get everything for free: caching, bundling, "
|
||||||
|
"dependency scanning, server/client rendering, composition. No parallel infrastructure."))
|
||||||
|
|
||||||
|
(~doc-section :title "Why Not a Style Dictionary?" :id "why"
|
||||||
|
(p "SX previously had a parallel CSS system: a style dictionary (JSON blob of "
|
||||||
|
"atom-to-declaration mappings), a " (code "StyleValue") " type threaded through "
|
||||||
|
"the evaluator and renderer, content-addressed hash class names (" (code "sx-a3f2b1")
|
||||||
|
"), runtime CSS injection, and a separate caching pipeline (cookies, localStorage).")
|
||||||
|
(p "This was ~3,000 lines of code across the spec, bootstrappers, and host implementations. "
|
||||||
|
"It was never adopted. The codebase voted with its feet: " (code ":class") " strings "
|
||||||
|
"with " (code "defcomp") " already covered every real use case.")
|
||||||
|
(p "The result of that system: elements in the DOM got opaque class names like "
|
||||||
|
(code "class=\"sx-a3f2b1\"") ". DevTools became useless. You couldn't inspect an "
|
||||||
|
"element and understand its styling. " (strong "That was a deal breaker.")))
|
||||||
|
|
||||||
|
(~doc-section :title "Key Advantages" :id "advantages"
|
||||||
|
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
|
||||||
|
(li (strong "Readable DOM: ") "Elements have real class names, not content-addressed "
|
||||||
|
"hashes. DevTools works.")
|
||||||
|
(li (strong "Data-driven styling: ") "Components receive data and decide styling. "
|
||||||
|
(code "(~metric :value 150)") " renders red because " (code "value > 100")
|
||||||
|
" — logic lives in the component, not a CSS preprocessor.")
|
||||||
|
(li (strong "One system: ") "No separate " (code "StyleValue") " type, no style "
|
||||||
|
"dictionary JSON, no injection pipeline. Components ARE the styling abstraction.")
|
||||||
|
(li (strong "One cache: ") "Component hash/localStorage handles everything. No "
|
||||||
|
"separate style dict caching.")
|
||||||
|
(li (strong "Composable: ") (code "(~card :elevated true (~metric :value v))")
|
||||||
|
" — styling composes like any other component.")
|
||||||
|
(li (strong "Strategy-agnostic: ") "A component can apply Tailwind classes, emit "
|
||||||
|
(code "<style>") " blocks, use inline styles, generate CSS custom properties, or "
|
||||||
|
"any combination. Swap strategies without touching call sites.")))
|
||||||
|
|
||||||
|
(~doc-section :title "What Changed" :id "changes"
|
||||||
|
(~doc-subsection :title "Removed (~3,000 lines)"
|
||||||
|
(ul :class "list-disc pl-5 space-y-1 text-stone-600 text-sm"
|
||||||
|
(li (code "StyleValue") " type and all plumbing (type checks in eval, render, serialize)")
|
||||||
|
(li (code "cssx.sx") " spec module (resolve-style, resolve-atom, split-variant, hash, injection)")
|
||||||
|
(li "Style dictionary JSON format, loading, caching (" (code "<script type=\"text/sx-styles\">") ", localStorage)")
|
||||||
|
(li (code "style_dict.py") " (782 lines) and " (code "style_resolver.py") " (254 lines)")
|
||||||
|
(li (code "css") " and " (code "merge-styles") " primitives")
|
||||||
|
(li "Platform interface: " (code "fnv1a-hash") ", " (code "compile-regex") ", " (code "make-style-value") ", " (code "inject-style-value"))
|
||||||
|
(li (code "defkeyframes") " special form")
|
||||||
|
(li "Style dict cookies and localStorage keys")))
|
||||||
|
|
||||||
|
(~doc-subsection :title "Kept"
|
||||||
|
(ul :class "list-disc pl-5 space-y-1 text-stone-600 text-sm"
|
||||||
|
(li (code "defstyle") " — simplified to bind any value (string, function, etc.)")
|
||||||
|
(li (code "tw.css") " — the compiled Tailwind stylesheet, delivered via CSS class tracking")
|
||||||
|
(li (code ":class") " attribute — just takes strings, no special-casing")
|
||||||
|
(li "CSS class delivery (" (code "SX-Css") " headers, " (code "<style id=\"sx-css\">") ")")
|
||||||
|
(li "All component infrastructure (defcomp, caching, bundling, deps)")))
|
||||||
|
|
||||||
|
(~doc-subsection :title "Added"
|
||||||
|
(p "Nothing. CSSX components are just " (code "defcomp") ". The only new thing is "
|
||||||
|
"a convention: components whose primary purpose is styling.")))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Patterns
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-patterns-content ()
|
||||||
|
(~doc-page :title "Patterns"
|
||||||
|
|
||||||
|
(~doc-section :title "Class Mapping" :id "class-mapping"
|
||||||
|
(p "The simplest pattern: a component that maps semantic keywords to class strings.")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~btn (&key variant disabled &rest children)\n (button\n :class (str \"px-4 py-2 rounded font-medium transition \"\n (case variant\n \"primary\" \"bg-blue-600 text-white hover:bg-blue-700\"\n \"danger\" \"bg-red-600 text-white hover:bg-red-700\"\n \"ghost\" \"bg-transparent hover:bg-stone-100\"\n \"bg-stone-200 hover:bg-stone-300\")\n (when disabled \" opacity-50 cursor-not-allowed\"))\n :disabled disabled\n children))"
|
||||||
|
"lisp")
|
||||||
|
(p "Consumers call " (code "(~btn :variant \"primary\" \"Submit\")") ". The Tailwind "
|
||||||
|
"classes are readable in DevTools but never repeated across call sites."))
|
||||||
|
|
||||||
|
(~doc-section :title "Data-Driven Styling" :id "data-driven"
|
||||||
|
(p "Styling that responds to data values — impossible with static CSS:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~metric (&key value label threshold)\n (let ((t (or threshold 10)))\n (div :class (str \"p-3 rounded font-bold \"\n (cond\n ((> value (* t 10)) \"bg-red-500 text-white\")\n ((> value t) \"bg-amber-200 text-amber-900\")\n (:else \"bg-green-100 text-green-800\")))\n (span :class \"text-sm\" label) \": \" (span (str value)))))"
|
||||||
|
"lisp")
|
||||||
|
(p "The component makes a " (em "decision") " about styling based on data. "
|
||||||
|
"No CSS preprocessor or class name convention can express \"red when value > 100\"."))
|
||||||
|
|
||||||
|
(~doc-section :title "Style Functions" :id "style-functions"
|
||||||
|
(p "Reusable style logic that returns class strings — no wrapping element needed:")
|
||||||
|
(highlight
|
||||||
|
"(define card-classes\n (fn (&key elevated bordered)\n (str \"rounded-lg p-4 \"\n (if elevated \"shadow-lg\" \"shadow-sm\")\n (when bordered \" border border-stone-200\"))))\n\n;; Usage:\n(div :class (card-classes :elevated true) ...)\n(article :class (card-classes :bordered true) ...)"
|
||||||
|
"lisp")
|
||||||
|
(p "Or with " (code "defstyle") " for named bindings:")
|
||||||
|
(highlight
|
||||||
|
"(defstyle card-base \"rounded-lg p-4 shadow-sm\")\n(defstyle card-elevated \"rounded-lg p-4 shadow-lg\")\n\n(div :class card-base ...)"
|
||||||
|
"lisp"))
|
||||||
|
|
||||||
|
(~doc-section :title "Responsive Layouts" :id "responsive"
|
||||||
|
(p "Components that encode responsive breakpoints:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~responsive-grid (&key cols &rest children)\n (div :class (str \"grid gap-4 \"\n (case (or cols 3)\n 1 \"grid-cols-1\"\n 2 \"grid-cols-1 md:grid-cols-2\"\n 3 \"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\"\n 4 \"grid-cols-2 md:grid-cols-3 lg:grid-cols-4\"))\n children))"
|
||||||
|
"lisp"))
|
||||||
|
|
||||||
|
(~doc-section :title "Emitting CSS Directly" :id "emitting-css"
|
||||||
|
(p "Components are not limited to referencing existing classes. They can generate "
|
||||||
|
"CSS — " (code "<style>") " tags, keyframes, custom properties — as part of their output:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~pulse (&key color duration &rest children)\n (<>\n (style (str \"@keyframes sx-pulse {\"\n \"0%,100% { opacity:1 } 50% { opacity:.5 } }\"))\n (div :style (str \"animation: sx-pulse \" (or duration \"2s\") \" infinite;\"\n \"color:\" (or color \"inherit\"))\n children)))"
|
||||||
|
"lisp")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~theme (&key primary surface &rest children)\n (<>\n (style (str \":root {\"\n \"--color-primary:\" (or primary \"#7c3aed\") \";\"\n \"--color-surface:\" (or surface \"#fafaf9\") \"}\"))\n children))"
|
||||||
|
"lisp")
|
||||||
|
(p "The CSS strategy is the component's private implementation detail. Consumers call "
|
||||||
|
(code "(~pulse :color \"red\" \"Loading...\")") " or "
|
||||||
|
(code "(~theme :primary \"#2563eb\" ...)") " without knowing or caring whether the "
|
||||||
|
"component uses classes, inline styles, generated rules, or all three."))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Async CSS
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-async-content ()
|
||||||
|
(~doc-page :title "Async CSS"
|
||||||
|
|
||||||
|
(~doc-section :title "The Pattern" :id "pattern"
|
||||||
|
(p "A CSSX component that needs CSS it doesn't have yet can "
|
||||||
|
(strong "fetch and cache it before rendering") ". This is just "
|
||||||
|
(code "~suspense") " combined with a style component — no new infrastructure:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~styled (&key css-url css-hash fallback &rest children)\n (if (css-cached? css-hash)\n ;; Already have it — render immediately\n children\n ;; Don't have it — suspense while we fetch\n (~suspense :id (str \"css-\" css-hash)\n :fallback (or fallback (span \"\"))\n (do\n (fetch-css css-url css-hash)\n children))))"
|
||||||
|
"lisp")
|
||||||
|
(p "The consumer never knows:")
|
||||||
|
(highlight
|
||||||
|
"(~styled :css-url \"/css/charts.css\" :css-hash \"abc123\"\n (~bar-chart :data metrics))"
|
||||||
|
"lisp"))
|
||||||
|
|
||||||
|
(~doc-section :title "Use Cases" :id "use-cases"
|
||||||
|
(~doc-subsection :title "Federated Components"
|
||||||
|
(p "A " (code "~btn") " from another site arrives via IPFS with a CID pointing "
|
||||||
|
"to its CSS. The component fetches and caches it before rendering. "
|
||||||
|
"No coordination needed between sites.")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~federated-widget (&key cid &rest children)\n (let ((css-cid (str cid \"/style.css\"))\n (cached (css-cached? css-cid)))\n (if cached\n children\n (~suspense :id (str \"fed-\" cid)\n :fallback (div :class \"animate-pulse bg-stone-100 rounded h-20\")\n (do (fetch-css (str \"https://ipfs.io/ipfs/\" css-cid) css-cid)\n children)))))"
|
||||||
|
"lisp"))
|
||||||
|
|
||||||
|
(~doc-subsection :title "Heavy UI Libraries"
|
||||||
|
(p "Code editors, chart libraries, rich text editors — their CSS only loads "
|
||||||
|
"when the component actually appears on screen:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~code-editor (&key language value on-change)\n (~styled :css-url \"/css/codemirror.css\" :css-hash (asset-hash \"codemirror\")\n :fallback (pre :class \"p-4 bg-stone-900 text-stone-300 rounded\" value)\n (div :class \"cm-editor\"\n :data-language language\n :data-value value)))"
|
||||||
|
"lisp"))
|
||||||
|
|
||||||
|
(~doc-subsection :title "Lazy Themes"
|
||||||
|
(p "Theme CSS loads on first use, then is instant on subsequent visits:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~lazy-theme (&key name &rest children)\n (let ((css-url (str \"/css/themes/\" name \".css\"))\n (hash (str \"theme-\" name)))\n (~styled :css-url css-url :css-hash hash\n :fallback children ;; render unstyled immediately\n children)))"
|
||||||
|
"lisp")))
|
||||||
|
|
||||||
|
(~doc-section :title "How It Composes" :id "composition"
|
||||||
|
(p "Async CSS composes with everything already in SX:")
|
||||||
|
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
||||||
|
(li (code "~suspense") " handles the async gap with fallback content")
|
||||||
|
(li "localStorage handles caching across sessions")
|
||||||
|
(li (code "<style id=\"sx-css\">") " is the injection target (same as CSS class delivery)")
|
||||||
|
(li "Component content-hashing tracks what the client has")
|
||||||
|
(li "No new types, no new delivery protocol, no new spec code")))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Live Styles
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-live-content ()
|
||||||
|
(~doc-page :title "Live Styles"
|
||||||
|
|
||||||
|
(~doc-section :title "Styles That Respond to Events" :id "concept"
|
||||||
|
(p "Combine " (code "~live") " (SSE) or " (code "~ws") " (WebSocket) with style "
|
||||||
|
"components, and you get styles that change in real-time in response to server "
|
||||||
|
"events. No new infrastructure — just components receiving data through a "
|
||||||
|
"persistent transport."))
|
||||||
|
|
||||||
|
(~doc-section :title "SSE: Live Theme Updates" :id "sse-theme"
|
||||||
|
(p "A " (code "~live") " component declares a persistent connection to an SSE "
|
||||||
|
"endpoint. When the server pushes a new event, " (code "resolveSuspense")
|
||||||
|
" replaces the content:")
|
||||||
|
(highlight
|
||||||
|
"(~live :src \"/api/stream/brand\"\n (~suspense :id \"theme\"\n (~theme :primary \"#7c3aed\" :surface \"#fafaf9\")))"
|
||||||
|
"lisp")
|
||||||
|
(p "Server pushes a new theme:")
|
||||||
|
(highlight
|
||||||
|
"event: sx-resolve\ndata: {\"id\": \"theme\", \"sx\": \"(~theme :primary \\\"#2563eb\\\" :surface \\\"#1e1e2e\\\")\"}"
|
||||||
|
"text")
|
||||||
|
(p "The " (code "~theme") " component emits CSS custom properties. Everything "
|
||||||
|
"using " (code "var(--color-primary)") " repaints instantly:")
|
||||||
|
(highlight
|
||||||
|
"(defcomp ~theme (&key primary surface)\n (style (str \":root {\"\n \"--color-primary:\" (or primary \"#7c3aed\") \";\"\n \"--color-surface:\" (or surface \"#fafaf9\") \"}\")))"
|
||||||
|
"lisp"))
|
||||||
|
|
||||||
|
(~doc-section :title "SSE: Live Dashboard Metrics" :id "sse-metrics"
|
||||||
|
(p "Style changes driven by live data — the component decides the visual treatment:")
|
||||||
|
(highlight
|
||||||
|
"(~live :src \"/api/stream/dashboard\"\n (~suspense :id \"cpu\"\n (~metric :value 0 :label \"CPU\" :threshold 80))\n (~suspense :id \"memory\"\n (~metric :value 0 :label \"Memory\" :threshold 90))\n (~suspense :id \"requests\"\n (~metric :value 0 :label \"RPS\" :threshold 1000)))"
|
||||||
|
"lisp")
|
||||||
|
(p "Server pushes updated values. " (code "~metric") " turns red when "
|
||||||
|
(code "value > threshold") " — the styling logic lives in the component, "
|
||||||
|
"not in CSS selectors or JavaScript event handlers."))
|
||||||
|
|
||||||
|
(~doc-section :title "WebSocket: Collaborative Design" :id "ws-design"
|
||||||
|
(p "Bidirectional channel for real-time collaboration. A designer adjusts a color, "
|
||||||
|
"all connected clients see the change:")
|
||||||
|
(highlight
|
||||||
|
"(~ws :src \"/ws/design-studio\"\n (~suspense :id \"canvas-theme\"\n (~theme :primary \"#7c3aed\")))"
|
||||||
|
"lisp")
|
||||||
|
(p "Client sends a color change:")
|
||||||
|
(highlight
|
||||||
|
";; Designer picks a new primary color\n(sx-send ws-conn '(theme-update :primary \"#dc2626\"))"
|
||||||
|
"lisp")
|
||||||
|
(p "Server broadcasts to all connected clients via " (code "sx-resolve") " — "
|
||||||
|
"every client's " (code "~theme") " component re-renders with the new color."))
|
||||||
|
|
||||||
|
(~doc-section :title "Why This Works" :id "why"
|
||||||
|
(p "Every one of these patterns is just a " (code "defcomp") " receiving data "
|
||||||
|
"through a persistent transport. The styling strategy — CSS custom properties, "
|
||||||
|
"class swaps, inline styles, " (code "<style>") " blocks — is the component's "
|
||||||
|
"private business. The transport doesn't know or care.")
|
||||||
|
(p "A parallel style system would have needed its own streaming, its own caching, "
|
||||||
|
"its own delta protocol for each of these use cases — duplicating what components "
|
||||||
|
"already do.")
|
||||||
|
(p :class "mt-4 text-stone-500 italic"
|
||||||
|
"Note: ~live and ~ws are planned (see Live Streaming). The patterns shown here "
|
||||||
|
"will work as described once the streaming transport is implemented. The component "
|
||||||
|
"and suspense infrastructure they depend on already exists."))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Comparison with CSS Technologies
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-comparison-content ()
|
||||||
|
(~doc-page :title "Comparisons"
|
||||||
|
|
||||||
|
(~doc-section :title "styled-components / Emotion" :id "styled-components"
|
||||||
|
(p (a :href "https://styled-components.com" :class "text-violet-600 hover:underline" "styled-components")
|
||||||
|
" pioneered the idea that styling belongs in components. But it generates CSS "
|
||||||
|
"at runtime, injects " (code "<style>") " tags, and produces opaque hashed class "
|
||||||
|
"names (" (code "class=\"sc-bdfBwQ fNMpVx\"") "). Open DevTools and you see gibberish. "
|
||||||
|
"It also carries significant runtime cost — parsing CSS template literals, hashing, "
|
||||||
|
"deduplicating — and needs a separate SSR extraction step (" (code "ServerStyleSheet") ").")
|
||||||
|
(p "CSSX components share the core insight (" (em "styling is a component concern")
|
||||||
|
") but without the runtime machinery. When a component applies Tailwind classes, "
|
||||||
|
"there's zero CSS generation overhead. When it does emit " (code "<style>")
|
||||||
|
" blocks, it's explicit — not hidden behind a tagged template literal. "
|
||||||
|
"And the DOM is always readable."))
|
||||||
|
|
||||||
|
(~doc-section :title "CSS Modules" :id "css-modules"
|
||||||
|
(p (a :href "https://github.com/css-modules/css-modules" :class "text-violet-600 hover:underline" "CSS Modules")
|
||||||
|
" scope class names to avoid collisions by rewriting them at build time: "
|
||||||
|
(code ".button") " becomes " (code ".button_abc123")
|
||||||
|
". This solves the global namespace problem but creates the same opacity issue — "
|
||||||
|
"hashed names in the DOM that you can't grep for or reason about.")
|
||||||
|
(p "CSSX components don't need scoping because component boundaries already provide "
|
||||||
|
"isolation. A " (code "~btn") " owns its markup. There's nothing to collide with."))
|
||||||
|
|
||||||
|
(~doc-section :title "Tailwind CSS" :id "tailwind"
|
||||||
|
(p "Tailwind is " (em "complementary") ", not competitive. CSSX components are the "
|
||||||
|
"semantic layer on top. Raw Tailwind in markup — "
|
||||||
|
(code ":class \"px-4 py-2 bg-blue-600 text-white font-medium rounded hover:bg-blue-700\"")
|
||||||
|
" — is powerful but verbose and duplicated across call sites.")
|
||||||
|
(p "A CSSX component wraps that string once: " (code "(~btn :variant \"primary\" \"Submit\")")
|
||||||
|
". The Tailwind classes are still there, readable in DevTools, but consumers don't "
|
||||||
|
"repeat them. This is the same pattern Tailwind's own docs recommend ("
|
||||||
|
(em "\"extracting components\"") ") — CSSX components are just SX's native way of doing it."))
|
||||||
|
|
||||||
|
(~doc-section :title "Vanilla Extract" :id "vanilla-extract"
|
||||||
|
(p (a :href "https://vanilla-extract.style" :class "text-violet-600 hover:underline" "Vanilla Extract")
|
||||||
|
" is zero-runtime CSS-in-JS: styles are written in TypeScript, compiled to static "
|
||||||
|
"CSS at build time, and referenced by generated class names. It avoids the runtime "
|
||||||
|
"cost of styled-components but still requires a build step, a bundler plugin, and "
|
||||||
|
"TypeScript. The generated class names are again opaque.")
|
||||||
|
(p "CSSX components need no build step for styling — they're evaluated at render time "
|
||||||
|
"like any other component. And since the component chooses its own strategy, it can "
|
||||||
|
"reference pre-built classes (zero runtime) " (em "or") " generate CSS on the fly — "
|
||||||
|
"same API either way."))
|
||||||
|
|
||||||
|
(~doc-section :title "Design Tokens / Style Dictionary" :id "design-tokens"
|
||||||
|
(p "The " (a :href "https://amzn.github.io/style-dictionary/" :class "text-violet-600 hover:underline" "Style Dictionary")
|
||||||
|
" pattern — a JSON/YAML file mapping token names to values, compiled to "
|
||||||
|
"platform-specific output — is essentially what the old CSSX was. It's the "
|
||||||
|
"industry standard for design systems.")
|
||||||
|
(p "The problem is that it's a parallel system: separate file format, separate build "
|
||||||
|
"pipeline, separate caching, separate tooling. CSSX components eliminate all of "
|
||||||
|
"that by expressing tokens as component parameters: "
|
||||||
|
(code "(~theme :primary \"#7c3aed\")") " instead of "
|
||||||
|
(code "{\"color\": {\"primary\": {\"value\": \"#7c3aed\"}}}")
|
||||||
|
". Same result, no parallel infrastructure."))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; Philosophy
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-philosophy-content ()
|
||||||
|
(~doc-page :title "Philosophy"
|
||||||
|
|
||||||
|
(~doc-section :title "The Collapse" :id "collapse"
|
||||||
|
(p "The web has spent two decades building increasingly complex CSS tooling: "
|
||||||
|
"preprocessors, CSS-in-JS, atomic CSS, utility frameworks, design tokens, style "
|
||||||
|
"dictionaries. Each solves a real problem but adds a new system with its own "
|
||||||
|
"caching, bundling, and mental model.")
|
||||||
|
(p "CSSX components collapse all of this back to the simplest possible thing: "
|
||||||
|
(strong "a function that takes data and returns markup with classes.")
|
||||||
|
" That's what a component already is. There is no separate styling system because "
|
||||||
|
"there doesn't need to be."))
|
||||||
|
|
||||||
|
(~doc-section :title "Proof by Deletion" :id "proof"
|
||||||
|
(p "The strongest validation: we built the full parallel system — style dictionary, "
|
||||||
|
"StyleValue type, content-addressed hashing, runtime injection, localStorage "
|
||||||
|
"caching — and then deleted it because nobody used it. The codebase already had "
|
||||||
|
"the answer: " (code "defcomp") " with " (code ":class") " strings.")
|
||||||
|
(p "3,000 lines of infrastructure removed. Zero lines added. Every use case still works."))
|
||||||
|
|
||||||
|
(~doc-section :title "The Right Abstraction Level" :id "abstraction"
|
||||||
|
(p "CSS-in-JS puts styling " (em "below") " components — you style elements, then compose "
|
||||||
|
"them. Utility CSS puts styling " (em "beside") " components — classes in markup, logic "
|
||||||
|
"elsewhere. Both create a seam between what something does and how it looks.")
|
||||||
|
(p "CSSX components put styling " (em "inside") " components — at the same level as "
|
||||||
|
"structure and behavior. A " (code "~metric") " component knows its own thresholds, "
|
||||||
|
"its own color scheme, its own responsive behavior. Styling is just another "
|
||||||
|
"decision the component makes, not a separate concern."))
|
||||||
|
|
||||||
|
(~doc-section :title "Relationship to Other Plans" :id "relationships"
|
||||||
|
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
|
||||||
|
(li (strong "Content-Addressed Components: ") "CSSX components get CIDs like any "
|
||||||
|
"other component. A " (code "~btn") " from one site can be shared to another via "
|
||||||
|
"IPFS. No " (code ":css-atoms") " manifest field needed — the component carries "
|
||||||
|
"its own styling logic.")
|
||||||
|
(li (strong "Isomorphic Rendering: ") "Components render the same on server and client. "
|
||||||
|
"No style injection timing issues, no FOUC from late CSS loading.")
|
||||||
|
(li (strong "Component Bundling: ") "deps.sx already handles transitive component deps. "
|
||||||
|
"Style components are just more components in the bundle — no separate style bundling.")
|
||||||
|
(li (strong "Live Streaming: ") "SSE and WebSocket transports push data to components. "
|
||||||
|
"Style components react to that data like any other component — no separate style "
|
||||||
|
"streaming protocol.")))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; CSS Delivery
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defcomp ~cssx-delivery-content ()
|
||||||
|
(~doc-page :title "CSS Delivery"
|
||||||
|
|
||||||
|
(~doc-section :title "Multiple Strategies" :id "strategies"
|
||||||
|
(p "A CSSX component chooses its own styling strategy — and each strategy has its "
|
||||||
|
"own delivery path:")
|
||||||
|
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
||||||
|
(li (strong "Tailwind classes: ") "Delivered via the on-demand protocol below. "
|
||||||
|
"The server ships only the rules the page actually uses.")
|
||||||
|
(li (strong "Inline styles: ") "No delivery needed — " (code ":style") " attributes "
|
||||||
|
"are part of the markup.")
|
||||||
|
(li (strong "Emitted " (code "<style>") " blocks: ") "Components can emit CSS rules "
|
||||||
|
"directly. They arrive as part of the rendered HTML — keyframes, custom properties, "
|
||||||
|
"scoped rules, anything.")
|
||||||
|
(li (strong "External stylesheets: ") "Components can reference pre-loaded CSS files "
|
||||||
|
"or lazy-load them via " (code "~suspense") " (see " (a :href "/cssx/async" "Async CSS") ").")
|
||||||
|
(li (strong "Custom properties: ") "A " (code "~theme") " component sets "
|
||||||
|
(code "--color-primary") " etc. via a " (code "<style>") " block. Everything "
|
||||||
|
"using " (code "var()") " repaints automatically."))
|
||||||
|
(p "The protocol below handles the Tailwind utility class case — the most common "
|
||||||
|
"strategy — but it's not the only game in town."))
|
||||||
|
|
||||||
|
(~doc-section :title "On-Demand Tailwind Delivery" :id "on-demand"
|
||||||
|
(p "When components use Tailwind utility classes, SX ships " (em "only the CSS rules "
|
||||||
|
"actually used on the page") ", computed at render time. No build step, no purging, "
|
||||||
|
"no unused CSS.")
|
||||||
|
(p "The server pre-parses the full Tailwind CSS file into an in-memory registry at "
|
||||||
|
"startup. When a response is rendered, SX scans all " (code ":class") " values in "
|
||||||
|
"the output, looks up only those classes, and embeds the matching rules."))
|
||||||
|
|
||||||
|
(~doc-section :title "The Protocol" :id "protocol"
|
||||||
|
(p "First page load gets the full set of used rules. Subsequent navigations send a "
|
||||||
|
"hash of what the client already has, and the server ships only the delta:")
|
||||||
|
(highlight
|
||||||
|
"# First page load:\nGET / HTTP/1.1\n\nHTTP/1.1 200 OK\nContent-Type: text/html\n# Full CSS in <style id=\"sx-css\"> + hash in <meta name=\"sx-css-classes\">\n\n# Subsequent navigation:\nGET /about HTTP/1.1\nSX-Css: a1b2c3d4\n\nHTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: e5f6g7h8\nSX-Css-Add: bg-blue-500,text-white,rounded-lg\n# Only new rules in <style data-sx-css>"
|
||||||
|
"bash")
|
||||||
|
(p "The client merges new rules into the existing " (code "<style id=\"sx-css\">")
|
||||||
|
" block and updates its hash. No flicker, no duplicate rules."))
|
||||||
|
|
||||||
|
(~doc-section :title "Component-Aware Scanning" :id "scanning"
|
||||||
|
(p "CSS scanning happens at two levels:")
|
||||||
|
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
||||||
|
(li (strong "Registration time: ") "When a component is defined via " (code "defcomp")
|
||||||
|
", its body is scanned for class strings. The component records which CSS classes "
|
||||||
|
"it uses.")
|
||||||
|
(li (strong "Render time: ") "The rendered output is scanned for any classes the "
|
||||||
|
"static scan missed — dynamically constructed class strings, conditional classes, etc."))
|
||||||
|
(p "This means the CSS registry knows roughly what a page needs before rendering, "
|
||||||
|
"and catches any stragglers after."))
|
||||||
|
|
||||||
|
(~doc-section :title "Trade-offs" :id "tradeoffs"
|
||||||
|
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
|
||||||
|
(li (strong "Full Tailwind in memory: ") "The parsed CSS registry is ~4MB. This is "
|
||||||
|
"a one-time startup cost per app instance.")
|
||||||
|
(li (strong "Regex scanning: ") "Class detection uses regex, so dynamically "
|
||||||
|
"constructed class names (e.g. " (code "(str \"bg-\" color \"-500\")") ") can be "
|
||||||
|
"missed. Use complete class strings in " (code "case") "/" (code "cond") " branches.")
|
||||||
|
(li (strong "No @apply: ") "Tailwind's " (code "@apply") " is a build-time feature. "
|
||||||
|
"On-demand delivery works with utility classes directly.")
|
||||||
|
(li (strong "Tailwind-shaped: ") "The registry parser understands Tailwind's naming "
|
||||||
|
"conventions. Non-Tailwind CSS works via " (code "<style>") " blocks in components.")))))
|
||||||
@@ -85,28 +85,6 @@
|
|||||||
"Forms marked with a tail position enable " (a :href "/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
|
"Forms marked with a tail position enable " (a :href "/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
|
||||||
(div :class "space-y-10" forms))))
|
(div :class "space-y-10" forms))))
|
||||||
|
|
||||||
(defcomp ~docs-css-content ()
|
|
||||||
(~doc-page :title "On-Demand CSS"
|
|
||||||
(~doc-section :title "How it works" :id "how"
|
|
||||||
(p :class "text-stone-600"
|
|
||||||
"sx scans every response for CSS class names used in :class attributes. It looks up only those classes in a pre-parsed Tailwind CSS registry and ships just the rules that are needed. No build step. No purging. No unused CSS.")
|
|
||||||
(p :class "text-stone-600"
|
|
||||||
"On the first page load, the full set of used classes is embedded in a <style> block. A hash of the class set is stored. On subsequent navigations, the client sends the hash in the SX-Css header. The server computes the diff and sends only new rules via SX-Css-Add and a <style data-sx-css> block."))
|
|
||||||
(~doc-section :title "The protocol" :id "protocol"
|
|
||||||
(~doc-code :code (highlight "# First page load:\nGET / HTTP/1.1\n\nHTTP/1.1 200 OK\nContent-Type: text/html\n# Full CSS in <style id=\"sx-css\"> + hash in <meta name=\"sx-css-classes\">\n\n# Subsequent navigation:\nGET /about HTTP/1.1\nSX-Css: a1b2c3d4\n\nHTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: e5f6g7h8\nSX-Css-Add: bg-blue-500,text-white,rounded-lg\n# Only new rules in <style data-sx-css>" "bash")))
|
|
||||||
(~doc-section :title "Advantages" :id "advantages"
|
|
||||||
(ul :class "space-y-2 text-stone-600"
|
|
||||||
(li "Zero build step — no Tailwind CLI, no PostCSS, no purging")
|
|
||||||
(li "Exact CSS — never ships a rule that isn't used on the page")
|
|
||||||
(li "Incremental — subsequent navigations only ship new rules")
|
|
||||||
(li "Component-aware — pre-scans component definitions at registration time")))
|
|
||||||
(~doc-section :title "Disadvantages" :id "disadvantages"
|
|
||||||
(ul :class "space-y-2 text-stone-600"
|
|
||||||
(li "Requires the full Tailwind CSS file loaded in memory at startup (~4MB parsed)")
|
|
||||||
(li "Regex-based class scanning — can miss dynamically constructed class names")
|
|
||||||
(li "No @apply support — classes must be used directly")
|
|
||||||
(li "Tied to Tailwind's utility class naming conventions")))))
|
|
||||||
|
|
||||||
(defcomp ~docs-server-rendering-content ()
|
(defcomp ~docs-server-rendering-content ()
|
||||||
(~doc-page :title "Server Rendering"
|
(~doc-page :title "Server Rendering"
|
||||||
(~doc-section :title "Python API" :id "python"
|
(~doc-section :title "Python API" :id "python"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
(let* ((sc "aria-selected:bg-violet-200 aria-selected:text-violet-900")
|
(let* ((sc "aria-selected:bg-violet-200 aria-selected:text-violet-900")
|
||||||
(items (list
|
(items (list
|
||||||
(dict :label "Docs" :href "/docs/introduction")
|
(dict :label "Docs" :href "/docs/introduction")
|
||||||
|
(dict :label "CSSX" :href "/cssx/")
|
||||||
(dict :label "Reference" :href "/reference/")
|
(dict :label "Reference" :href "/reference/")
|
||||||
(dict :label "Protocols" :href "/protocols/wire-format")
|
(dict :label "Protocols" :href "/protocols/wire-format")
|
||||||
(dict :label "Examples" :href "/examples/click-to-load")
|
(dict :label "Examples" :href "/examples/click-to-load")
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
(dict :label "Evaluator" :href "/docs/evaluator")
|
(dict :label "Evaluator" :href "/docs/evaluator")
|
||||||
(dict :label "Primitives" :href "/docs/primitives")
|
(dict :label "Primitives" :href "/docs/primitives")
|
||||||
(dict :label "Special Forms" :href "/docs/special-forms")
|
(dict :label "Special Forms" :href "/docs/special-forms")
|
||||||
(dict :label "CSS" :href "/docs/css")
|
|
||||||
(dict :label "Server Rendering" :href "/docs/server-rendering")))
|
(dict :label "Server Rendering" :href "/docs/server-rendering")))
|
||||||
|
|
||||||
(define reference-nav-items (list
|
(define reference-nav-items (list
|
||||||
@@ -55,13 +54,22 @@
|
|||||||
(dict :label "Request Abort" :href "/examples/sync-replace")
|
(dict :label "Request Abort" :href "/examples/sync-replace")
|
||||||
(dict :label "Retry" :href "/examples/retry")))
|
(dict :label "Retry" :href "/examples/retry")))
|
||||||
|
|
||||||
|
(define cssx-nav-items (list
|
||||||
|
(dict :label "Overview" :href "/cssx/")
|
||||||
|
(dict :label "Patterns" :href "/cssx/patterns")
|
||||||
|
(dict :label "Delivery" :href "/cssx/delivery")
|
||||||
|
(dict :label "Async CSS" :href "/cssx/async")
|
||||||
|
(dict :label "Live Styles" :href "/cssx/live")
|
||||||
|
(dict :label "Comparisons" :href "/cssx/comparisons")
|
||||||
|
(dict :label "Philosophy" :href "/cssx/philosophy")))
|
||||||
|
|
||||||
(define essays-nav-items (list
|
(define essays-nav-items (list
|
||||||
(dict :label "Why S-Expressions" :href "/essays/why-sexps"
|
(dict :label "Why S-Expressions" :href "/essays/why-sexps"
|
||||||
:summary "Why SX uses s-expressions instead of HTML templates, JSX, or any other syntax.")
|
:summary "Why SX uses s-expressions instead of HTML templates, JSX, or any other syntax.")
|
||||||
(dict :label "The htmx/React Hybrid" :href "/essays/htmx-react-hybrid"
|
(dict :label "The htmx/React Hybrid" :href "/essays/htmx-react-hybrid"
|
||||||
:summary "How SX combines the server-driven simplicity of htmx with the component model of React.")
|
:summary "How SX combines the server-driven simplicity of htmx with the component model of React.")
|
||||||
(dict :label "On-Demand CSS" :href "/essays/on-demand-css"
|
(dict :label "On-Demand CSS" :href "/essays/on-demand-css"
|
||||||
:summary "The CSSX system: keyword atoms resolved to class names, CSS rules injected on first use.")
|
:summary "How SX delivers only the CSS each page needs — server scans rendered classes, sends the delta.")
|
||||||
(dict :label "Client Reactivity" :href "/essays/client-reactivity"
|
(dict :label "Client Reactivity" :href "/essays/client-reactivity"
|
||||||
:summary "Reactive UI updates without a virtual DOM, diffing library, or build step.")
|
:summary "Reactive UI updates without a virtual DOM, diffing library, or build step.")
|
||||||
(dict :label "SX Native" :href "/essays/sx-native"
|
(dict :label "SX Native" :href "/essays/sx-native"
|
||||||
@@ -103,7 +111,6 @@
|
|||||||
(dict :label "SxEngine" :href "/specs/engine")
|
(dict :label "SxEngine" :href "/specs/engine")
|
||||||
(dict :label "Orchestration" :href "/specs/orchestration")
|
(dict :label "Orchestration" :href "/specs/orchestration")
|
||||||
(dict :label "Boot" :href "/specs/boot")
|
(dict :label "Boot" :href "/specs/boot")
|
||||||
(dict :label "CSSX" :href "/specs/cssx")
|
|
||||||
(dict :label "Continuations" :href "/specs/continuations")
|
(dict :label "Continuations" :href "/specs/continuations")
|
||||||
(dict :label "call/cc" :href "/specs/callcc")
|
(dict :label "call/cc" :href "/specs/callcc")
|
||||||
(dict :label "Deps" :href "/specs/deps")
|
(dict :label "Deps" :href "/specs/deps")
|
||||||
@@ -147,8 +154,6 @@
|
|||||||
:summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")
|
:summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")
|
||||||
(dict :label "SX CI Pipeline" :href "/plans/sx-ci"
|
(dict :label "SX CI Pipeline" :href "/plans/sx-ci"
|
||||||
:summary "Build, test, and deploy in s-expressions — CI pipelines as SX components.")
|
:summary "Build, test, and deploy in s-expressions — CI pipelines as SX components.")
|
||||||
(dict :label "CSSX Components" :href "/plans/cssx-components"
|
|
||||||
:summary "Styling as components — replace the style dictionary with regular defcomps that apply classes, respond to data, and compose naturally.")
|
|
||||||
(dict :label "Live Streaming" :href "/plans/live-streaming"
|
(dict :label "Live Streaming" :href "/plans/live-streaming"
|
||||||
:summary "SSE and WebSocket transports for re-resolving suspense slots after initial page load — live data, real-time collaboration.")))
|
:summary "SSE and WebSocket transports for re-resolving suspense slots after initial page load — live data, real-time collaboration.")))
|
||||||
|
|
||||||
@@ -177,7 +182,7 @@
|
|||||||
:prose "Special forms are the syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface. The registry covers control flow (if, when, cond, case, and, or), binding (let, letrec, define, set!), functions (lambda, defcomp, defmacro), sequencing (begin, do, thread-first), quoting (quote, quasiquote), continuations (reset, shift), guards (dynamic-wind), higher-order forms (map, filter, reduce), and domain-specific definitions (defstyle, defhandler, defpage, defquery, defaction).")
|
:prose "Special forms are the syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface. The registry covers control flow (if, when, cond, case, and, or), binding (let, letrec, define, set!), functions (lambda, defcomp, defmacro), sequencing (begin, do, thread-first), quoting (quote, quasiquote), continuations (reset, shift), guards (dynamic-wind), higher-order forms (map, filter, reduce), and domain-specific definitions (defstyle, defhandler, defpage, defquery, defaction).")
|
||||||
(dict :slug "renderer" :filename "render.sx" :title "Renderer"
|
(dict :slug "renderer" :filename "render.sx" :title "Renderer"
|
||||||
:desc "Shared rendering registries and utilities used by all adapters."
|
:desc "Shared rendering registries and utilities used by all adapters."
|
||||||
:prose "The renderer defines what is renderable and how arguments are parsed, but not the output format. It maintains registries of known HTML tags, SVG tags, void elements, and boolean attributes. It specifies how keyword arguments on elements become HTML attributes, how children are collected, and how special attributes (class, style, data-*) are handled. All three adapters (DOM, HTML, SX wire) share these definitions so they agree on what constitutes valid markup. The renderer also defines the StyleValue type used by the CSSX on-demand CSS system.")))
|
:prose "The renderer defines what is renderable and how arguments are parsed, but not the output format. It maintains registries of known HTML tags, SVG tags, void elements, and boolean attributes. It specifies how keyword arguments on elements become HTML attributes, how children are collected, and how special attributes (class, style, data-*) are handled. All three adapters (DOM, HTML, SX wire) share these definitions so they agree on what constitutes valid markup.")))
|
||||||
|
|
||||||
(define adapter-spec-items (list
|
(define adapter-spec-items (list
|
||||||
(dict :slug "adapter-dom" :filename "adapter-dom.sx" :title "DOM Adapter"
|
(dict :slug "adapter-dom" :filename "adapter-dom.sx" :title "DOM Adapter"
|
||||||
@@ -199,10 +204,7 @@
|
|||||||
(define browser-spec-items (list
|
(define browser-spec-items (list
|
||||||
(dict :slug "boot" :filename "boot.sx" :title "Boot"
|
(dict :slug "boot" :filename "boot.sx" :title "Boot"
|
||||||
:desc "Browser startup lifecycle: mount, hydrate, script processing."
|
:desc "Browser startup lifecycle: mount, hydrate, script processing."
|
||||||
:prose "Boot handles the browser startup sequence and provides the public API for mounting SX content. On page load it: (1) initializes CSS tracking, (2) loads the style dictionary from inline JSON, (3) processes <script type=\"text/sx\"> tags (component definitions and mount directives), (4) hydrates [data-sx] elements, and (5) activates the engine on all elements. It also provides the public mount/hydrate/update/render-component API, and the head element hoisting logic that moves <meta>, <title>, and <link> tags from rendered content into <head>.")
|
:prose "Boot handles the browser startup sequence and provides the public API for mounting SX content. On page load it: (1) initializes CSS tracking, (2) processes <script type=\"text/sx\"> tags (component definitions and mount directives), (3) hydrates [data-sx] elements, and (4) activates the engine on all elements. It also provides the public mount/hydrate/update/render-component API, and the head element hoisting logic that moves <meta>, <title>, and <link> tags from rendered content into <head>.")))
|
||||||
(dict :slug "cssx" :filename "cssx.sx" :title "CSSX"
|
|
||||||
:desc "On-demand CSS: style dictionary, keyword resolution, rule injection."
|
|
||||||
:prose "CSSX is the on-demand CSS system. It resolves keyword atoms (:flex, :gap-4, :hover:bg-sky-200) into StyleValue objects with content-addressed class names, injecting CSS rules into the document on first use. The style dictionary is a JSON blob containing: atoms (keyword to CSS declarations), pseudo-variants (hover:, focus:, etc.), responsive breakpoints (md:, lg:, etc.), keyframe animations, arbitrary value patterns, and child selector prefixes (space-x-, space-y-). Classes are only emitted when used, keeping the CSS payload minimal. The dictionary is typically served inline in a <script type=\"text/sx-styles\"> tag.")))
|
|
||||||
|
|
||||||
(define extension-spec-items (list
|
(define extension-spec-items (list
|
||||||
(dict :slug "continuations" :filename "continuations.sx" :title "Continuations"
|
(dict :slug "continuations" :filename "continuations.sx" :title "Continuations"
|
||||||
|
|||||||
124
sx/sx/plans.sx
124
sx/sx/plans.sx
@@ -2295,130 +2295,6 @@
|
|||||||
(td :class "px-3 py-2 text-stone-700" "Add CI primitive declarations"))))))))
|
(td :class "px-3 py-2 text-stone-700" "Add CI primitive declarations"))))))))
|
||||||
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; CSSX Components
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(defcomp ~plan-cssx-components-content ()
|
|
||||||
(~doc-page :title "CSSX Components"
|
|
||||||
|
|
||||||
(~doc-section :title "Context" :id "context"
|
|
||||||
(p "SX currently has a parallel CSS system: a style dictionary (JSON blob of atom-to-declaration mappings), a " (code "StyleValue") " type threaded through the evaluator and renderer, content-addressed hash class names (" (code "sx-a3f2b1") "), runtime CSS injection into " (code "<style id=\"sx-css\">") ", and a separate caching pipeline (" (code "<script type=\"text/sx-styles\">") ", localStorage, cookies).")
|
|
||||||
(p "This is ~300 lines of spec code (cssx.sx) plus platform interface (hash, regex, injection), plus server-side infrastructure (css_registry.py, tw.css parsing). All to solve one problem: " (em "resolving keyword atoms like ") (code ":flex :gap-4 :hover:bg-sky-200") (em " into CSS at render time."))
|
|
||||||
(p "The result: elements in the DOM get opaque class names like " (code "class=\"sx-a3f2b1\"") ". DevTools becomes useless. You can't inspect an element and understand its styling. " (strong "This is a deal breaker.")))
|
|
||||||
|
|
||||||
(~doc-section :title "The Idea" :id "idea"
|
|
||||||
(p (strong "Styling is just components.") " A CSSX component is a regular " (code "defcomp") " that decides how to style its children. It might apply Tailwind classes, or hand-written CSS classes, or inline styles, or generate rules at runtime. The implementation is the component's private business. The consumer just calls " (code "(~btn :variant \"primary\" \"Submit\")") " and doesn't care.")
|
|
||||||
(p "Because it's " (code "defcomp") ", you get everything for free: caching, bundling, dependency scanning, server/client rendering, composition. No parallel infrastructure.")
|
|
||||||
(p "Key advantages:")
|
|
||||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
|
||||||
(li (strong "Readable DOM: ") "Elements have real class names, not content-addressed hashes. DevTools works.")
|
|
||||||
(li (strong "Data-driven styling: ") "Components receive data and decide styling. " (code "(~metric :value 150)") " renders red because " (code "value > 100") " — logic lives in the component, not a CSS preprocessor.")
|
|
||||||
(li (strong "One system: ") "No separate " (code "StyleValue") " type, no style dictionary JSON, no " (code "<script type=\"text/sx-styles\">") ", no " (code "sx-css") " injection. Components ARE the styling abstraction.")
|
|
||||||
(li (strong "One cache: ") "Component hash/localStorage handles everything. No separate style dict caching.")
|
|
||||||
(li (strong "Composable: ") (code "(~card :elevated true (~metric :value v))") " — styling composes like any other component.")
|
|
||||||
(li (strong "Strategy-agnostic: ") "A component can apply Tailwind classes, emit " (code "<style>") " blocks, use inline styles, generate CSS custom properties, or any combination. The consumer never knows or cares. Swap strategies without touching call sites.")))
|
|
||||||
|
|
||||||
(~doc-section :title "Examples" :id "examples"
|
|
||||||
(~doc-subsection :title "Simple class mapping"
|
|
||||||
(p "A button component that maps variant keywords to class strings:")
|
|
||||||
(highlight
|
|
||||||
"(defcomp ~btn (&key variant disabled &rest children)\n (button\n :class (str \"px-4 py-2 rounded font-medium transition \"\n (case variant\n \"primary\" \"bg-blue-600 text-white hover:bg-blue-700\"\n \"danger\" \"bg-red-600 text-white hover:bg-red-700\"\n \"ghost\" \"bg-transparent hover:bg-stone-100\"\n \"bg-stone-200 hover:bg-stone-300\")\n (when disabled \" opacity-50 cursor-not-allowed\"))\n :disabled disabled\n children))"
|
|
||||||
"lisp"))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Data-driven styling"
|
|
||||||
(p "Styling that responds to data values — impossible with static CSS:")
|
|
||||||
(highlight
|
|
||||||
"(defcomp ~metric (&key value label threshold)\n (let ((t (or threshold 10)))\n (div :class (str \"p-3 rounded font-bold \"\n (cond\n ((> value (* t 10)) \"bg-red-500 text-white\")\n ((> value t) \"bg-amber-200 text-amber-900\")\n (:else \"bg-green-100 text-green-800\")))\n (span :class \"text-sm\" label) \": \" (span (str value)))))"
|
|
||||||
"lisp"))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Style functions"
|
|
||||||
(p "Reusable style logic without wrapping — returns class strings:")
|
|
||||||
(highlight
|
|
||||||
"(define card-classes\n (fn (&key elevated bordered)\n (str \"rounded-lg p-4 \"\n (if elevated \"shadow-lg\" \"shadow-sm\")\n (when bordered \" border border-stone-200\"))))\n\n;; Usage: (div :class (card-classes :elevated true) ...)"
|
|
||||||
"lisp"))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Responsive and interactive"
|
|
||||||
(p "Components can encode responsive breakpoints and interactive states as class strings — the same way you'd write Tailwind, but wrapped in a semantic component:")
|
|
||||||
(highlight
|
|
||||||
"(defcomp ~responsive-grid (&key cols &rest children)\n (div :class (str \"grid gap-4 \"\n (case (or cols 3)\n 1 \"grid-cols-1\"\n 2 \"grid-cols-1 md:grid-cols-2\"\n 3 \"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\"\n 4 \"grid-cols-2 md:grid-cols-3 lg:grid-cols-4\"))\n children))"
|
|
||||||
"lisp"))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Emitting CSS directly"
|
|
||||||
(p "Components are not limited to referencing existing classes. They can generate CSS — " (code "<style>") " tags, keyframes, custom properties — as part of their output:")
|
|
||||||
(highlight
|
|
||||||
"(defcomp ~pulse (&key color duration &rest children)\n (<>\n (style (str \"@keyframes sx-pulse {\"\n \"0%,100% { opacity:1 } 50% { opacity:.5 } }\"))\n (div :style (str \"animation: sx-pulse \" (or duration \"2s\") \" infinite;\"\n \"color:\" (or color \"inherit\"))\n children)))\n\n(defcomp ~theme (&key primary surface &rest children)\n (<>\n (style (str \":root {\"\n \"--color-primary:\" (or primary \"#7c3aed\") \";\"\n \"--color-surface:\" (or surface \"#fafaf9\") \"}\"))\n children))"
|
|
||||||
"lisp")
|
|
||||||
(p "The CSS strategy is the component's private implementation detail. Consumers call " (code "(~pulse :color \"red\" \"Loading...\")") " or " (code "(~theme :primary \"#2563eb\" ...)") " without knowing or caring whether the component uses classes, inline styles, generated rules, or all three.")))
|
|
||||||
|
|
||||||
(~doc-section :title "What Changes" :id "changes"
|
|
||||||
|
|
||||||
(~doc-subsection :title "Remove"
|
|
||||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
|
||||||
(li (code "StyleValue") " type and all plumbing (type checks in eval, render, serialize)")
|
|
||||||
(li (code "cssx.sx") " spec module (~300 lines: resolve-style, resolve-atom, split-variant, hash, injection)")
|
|
||||||
(li "Style dictionary JSON format, loading, caching (" (code "<script type=\"text/sx-styles\">") ", " (code "initStyleDict") ", " (code "parseAndLoadStyleDict") ")")
|
|
||||||
(li (code "<style id=\"sx-css\">") " runtime CSS injection system")
|
|
||||||
(li (code "css_registry.py") " server-side (builds style dictionary from tw.css)")
|
|
||||||
(li "Style dict cookies (" (code "sx-styles-hash") "), localStorage keys (" (code "sx-styles-src") ")")
|
|
||||||
(li "Platform interface: " (code "fnv1a-hash") ", " (code "compile-regex") ", " (code "regex-match") ", " (code "regex-replace-groups") ", " (code "make-style-value") ", " (code "inject-style-value"))))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Keep"
|
|
||||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
|
||||||
(li (code "defstyle") " — already just " (code "(defstyle name expr)") " which binds name to a value. Stays as sugar for defining reusable style values/functions. No " (code "StyleValue") " type needed — the value can be a string, a function, anything.")
|
|
||||||
(li (code "defkeyframes") " — could stay if we want declarative keyframe definitions. Or could become a component/function too.")
|
|
||||||
(li (code "tw.css") " — the compiled Tailwind stylesheet. Components reference its classes directly. No runtime resolution needed.")
|
|
||||||
(li (code ":class") " attribute — just takes strings now, no " (code "StyleValue") " special-casing.")))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Add"
|
|
||||||
(p "Nothing new to the spec. CSSX components are just " (code "defcomp") ". The only new thing is a convention: components whose primary purpose is styling. They live in the same component files, cache the same way, bundle the same way.")))
|
|
||||||
|
|
||||||
(~doc-section :title "Migration" :id "migration"
|
|
||||||
(p "The existing codebase uses " (code ":class") " with plain Tailwind strings everywhere already. The CSSX style dictionary was an alternative path that was never widely adopted. Migration is mostly deletion:")
|
|
||||||
(ol :class "list-decimal pl-5 space-y-2 text-stone-700"
|
|
||||||
(li "Remove " (code "StyleValue") " type from " (code "types.py") ", " (code "render.sx") ", " (code "eval.sx") ", bootstrappers")
|
|
||||||
(li "Remove " (code "cssx.sx") " from spec modules and bootstrapper")
|
|
||||||
(li "Remove " (code "css_registry.py") " and style dict generation pipeline")
|
|
||||||
(li "Remove style dict loading from " (code "boot.sx") " (" (code "initStyleDict") ", " (code "queryStyleScripts") ")")
|
|
||||||
(li "Remove style-related cookies and localStorage from " (code "boot.sx") " platform interface")
|
|
||||||
(li "Remove " (code "StyleValue") " special-casing from " (code "render-attrs") " in " (code "render.sx") " and DOM adapter")
|
|
||||||
(li "Simplify " (code ":class") " / " (code ":style") " attribute handling — just strings")
|
|
||||||
(li "Convert any existing " (code "defstyle") " uses to return plain class strings instead of " (code "StyleValue") " objects"))
|
|
||||||
(p :class "mt-4 text-stone-600 italic" "Net effect: hundreds of lines of spec and infrastructure removed, zero new lines added. The component system already does everything CSSX was trying to do."))
|
|
||||||
|
|
||||||
(~doc-section :title "Relationship to Other Plans" :id "relationships"
|
|
||||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
|
||||||
(li (strong "Content-Addressed Components: ") "CSSX components get CIDs like any other component. A " (code "~btn") " from one site can be shared to another via IPFS. No " (code ":css-atoms") " manifest field needed — the component carries its own styling logic.")
|
|
||||||
(li (strong "Isomorphic Rendering: ") "Components render the same on server and client. No style injection timing issues, no FOUC from late CSS loading.")
|
|
||||||
(li (strong "Component Bundling: ") "deps.sx already handles transitive component deps. Style components are just more components in the bundle — no separate style bundling.")))
|
|
||||||
|
|
||||||
(~doc-section :title "Comparison with CSS Technologies" :id "comparison"
|
|
||||||
(p "CSSX components share DNA with several existing approaches but avoid the problems that make each one painful at scale.")
|
|
||||||
|
|
||||||
(~doc-subsection :title "styled-components / Emotion"
|
|
||||||
(p (a :href "https://styled-components.com" :class "text-violet-600 hover:underline" "styled-components") " pioneered the idea that styling belongs in components. But it generates CSS at runtime, injects " (code "<style>") " tags, and produces opaque hashed class names (" (code "class=\"sc-bdfBwQ fNMpVx\"") "). Open DevTools and you see gibberish. It also carries significant runtime cost — parsing CSS template literals, hashing, deduplicating — and needs a separate SSR extraction step (" (code "ServerStyleSheet") ").")
|
|
||||||
(p "CSSX components share the core insight (" (em "styling is a component concern") ") but without the runtime machinery. When a component applies Tailwind classes, there's zero CSS generation overhead. When it does emit " (code "<style>") " blocks, it's explicit — not hidden behind a tagged template literal. And the DOM is always readable."))
|
|
||||||
|
|
||||||
(~doc-subsection :title "CSS Modules"
|
|
||||||
(p (a :href "https://github.com/css-modules/css-modules" :class "text-violet-600 hover:underline" "CSS Modules") " scope class names to avoid collisions by rewriting them at build time: " (code ".button") " becomes " (code ".button_abc123") ". This solves the global namespace problem but creates the same opacity issue — hashed names in the DOM that you can't grep for or reason about.")
|
|
||||||
(p "CSSX components don't need scoping because component boundaries already provide isolation. A " (code "~btn") " owns its markup. There's nothing to collide with."))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Tailwind CSS"
|
|
||||||
(p "Tailwind is " (em "complementary") ", not competitive. CSSX components are the semantic layer on top. Raw Tailwind in markup — " (code ":class \"px-4 py-2 bg-blue-600 text-white font-medium rounded hover:bg-blue-700\"") " — is powerful but verbose and duplicated across call sites.")
|
|
||||||
(p "A CSSX component wraps that string once: " (code "(~btn :variant \"primary\" \"Submit\")") ". The Tailwind classes are still there, readable in DevTools, but consumers don't repeat them. This is the same pattern Tailwind's own docs recommend (" (em "\"extracting components\"") ") — CSSX components are just SX's native way of doing it."))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Vanilla Extract"
|
|
||||||
(p (a :href "https://vanilla-extract.style" :class "text-violet-600 hover:underline" "Vanilla Extract") " is zero-runtime CSS-in-JS: styles are written in TypeScript, compiled to static CSS at build time, and referenced by generated class names. It avoids the runtime cost of styled-components but still requires a build step, a bundler plugin, and TypeScript. The generated class names are again opaque.")
|
|
||||||
(p "CSSX components need no build step for styling — they're evaluated at render time like any other component. And since the component chooses its own strategy, it can reference pre-built classes (zero runtime) " (em "or") " generate CSS on the fly — same API either way."))
|
|
||||||
|
|
||||||
(~doc-subsection :title "Design Tokens / Style Dictionary"
|
|
||||||
(p "The " (a :href "https://amzn.github.io/style-dictionary/" :class "text-violet-600 hover:underline" "Style Dictionary") " pattern — a JSON/YAML file mapping token names to values, compiled to platform-specific output — is essentially what the old CSSX was. It's the industry standard for design systems.")
|
|
||||||
(p "The problem is that it's a parallel system: separate file format, separate build pipeline, separate caching, separate tooling. CSSX components eliminate all of that by expressing tokens as component parameters: " (code "(~theme :primary \"#7c3aed\")") " instead of " (code "{\"color\": {\"primary\": {\"value\": \"#7c3aed\"}}}") ". Same result, no parallel infrastructure.")))
|
|
||||||
|
|
||||||
(~doc-section :title "Philosophy" :id "philosophy"
|
|
||||||
(p "The web has spent two decades building increasingly complex CSS tooling: preprocessors, CSS-in-JS, atomic CSS, utility frameworks, design tokens, style dictionaries. Each solves a real problem but adds a new system with its own caching, bundling, and mental model.")
|
|
||||||
(p "CSSX components collapse all of this back to the simplest possible thing: " (strong "a function that takes data and returns markup with classes.") " That's what a component already is. There is no separate styling system because there doesn't need to be."))))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; Live Streaming — SSE & WebSocket
|
;; Live Streaming — SSE & WebSocket
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -46,7 +46,6 @@
|
|||||||
:prims (~doc-primitives-tables :primitives (primitives-data)))
|
:prims (~doc-primitives-tables :primitives (primitives-data)))
|
||||||
"special-forms" (~docs-special-forms-content
|
"special-forms" (~docs-special-forms-content
|
||||||
:forms (~doc-special-forms-tables :forms (special-forms-data)))
|
:forms (~doc-special-forms-tables :forms (special-forms-data)))
|
||||||
"css" (~docs-css-content)
|
|
||||||
"server-rendering" (~docs-server-rendering-content)
|
"server-rendering" (~docs-server-rendering-content)
|
||||||
:else (~docs-introduction-content)))
|
:else (~docs-introduction-content)))
|
||||||
|
|
||||||
@@ -286,6 +285,40 @@
|
|||||||
"no-alternative" (~essay-no-alternative)
|
"no-alternative" (~essay-no-alternative)
|
||||||
:else (~essays-index-content)))
|
:else (~essays-index-content)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; CSSX section
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defpage cssx-index
|
||||||
|
:path "/cssx/"
|
||||||
|
:auth :public
|
||||||
|
:layout (:sx-section
|
||||||
|
:section "CSSX"
|
||||||
|
:sub-label "CSSX"
|
||||||
|
:sub-href "/cssx/"
|
||||||
|
:sub-nav (~section-nav :items cssx-nav-items :current "Overview")
|
||||||
|
:selected "Overview")
|
||||||
|
:content (~cssx-overview-content))
|
||||||
|
|
||||||
|
(defpage cssx-page
|
||||||
|
:path "/cssx/<slug>"
|
||||||
|
:auth :public
|
||||||
|
:layout (:sx-section
|
||||||
|
:section "CSSX"
|
||||||
|
:sub-label "CSSX"
|
||||||
|
:sub-href "/cssx/"
|
||||||
|
:sub-nav (~section-nav :items cssx-nav-items
|
||||||
|
:current (find-current cssx-nav-items slug))
|
||||||
|
:selected (or (find-current cssx-nav-items slug) ""))
|
||||||
|
:content (case slug
|
||||||
|
"patterns" (~cssx-patterns-content)
|
||||||
|
"delivery" (~cssx-delivery-content)
|
||||||
|
"async" (~cssx-async-content)
|
||||||
|
"live" (~cssx-live-content)
|
||||||
|
"comparisons" (~cssx-comparison-content)
|
||||||
|
"philosophy" (~cssx-philosophy-content)
|
||||||
|
:else (~cssx-overview-content)))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; Specs section
|
;; Specs section
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
@@ -546,7 +579,6 @@
|
|||||||
"glue-decoupling" (~plan-glue-decoupling-content)
|
"glue-decoupling" (~plan-glue-decoupling-content)
|
||||||
"social-sharing" (~plan-social-sharing-content)
|
"social-sharing" (~plan-social-sharing-content)
|
||||||
"sx-ci" (~plan-sx-ci-content)
|
"sx-ci" (~plan-sx-ci-content)
|
||||||
"cssx-components" (~plan-cssx-components-content)
|
|
||||||
"live-streaming" (~plan-live-streaming-content)
|
"live-streaming" (~plan-live-streaming-content)
|
||||||
:else (~plans-index-content)))
|
:else (~plans-index-content)))
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def _special_forms_data() -> dict:
|
|||||||
"filter": "Higher-Order Forms", "reduce": "Higher-Order Forms",
|
"filter": "Higher-Order Forms", "reduce": "Higher-Order Forms",
|
||||||
"some": "Higher-Order Forms", "every?": "Higher-Order Forms",
|
"some": "Higher-Order Forms", "every?": "Higher-Order Forms",
|
||||||
"for-each": "Higher-Order Forms",
|
"for-each": "Higher-Order Forms",
|
||||||
"defstyle": "Domain Definitions", "defkeyframes": "Domain Definitions",
|
"defstyle": "Domain Definitions",
|
||||||
"defhandler": "Domain Definitions", "defpage": "Domain Definitions",
|
"defhandler": "Domain Definitions", "defpage": "Domain Definitions",
|
||||||
"defquery": "Domain Definitions", "defaction": "Domain Definitions",
|
"defquery": "Domain Definitions", "defaction": "Domain Definitions",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user