Merge branch 'worktree-api-urls' into macros
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||||
var SX_VERSION = "2026-03-12T22:55:39Z";
|
var SX_VERSION = "2026-03-13T02:54:01Z";
|
||||||
|
|
||||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||||
@@ -83,6 +83,12 @@
|
|||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
|
|
||||||
|
function SxSpread(attrs) { this.attrs = attrs || {}; }
|
||||||
|
SxSpread.prototype._spread = true;
|
||||||
|
|
||||||
|
var _collectBuckets = {};
|
||||||
|
var _provideStacks = {};
|
||||||
|
|
||||||
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; }
|
||||||
|
|
||||||
@@ -118,6 +124,7 @@
|
|||||||
if (x._component) return "component";
|
if (x._component) return "component";
|
||||||
if (x._island) return "island";
|
if (x._island) return "island";
|
||||||
if (x._signal) return "signal";
|
if (x._signal) return "signal";
|
||||||
|
if (x._spread) return "spread";
|
||||||
if (x._macro) return "macro";
|
if (x._macro) return "macro";
|
||||||
if (x._raw) return "raw-html";
|
if (x._raw) return "raw-html";
|
||||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||||
@@ -140,6 +147,51 @@
|
|||||||
}
|
}
|
||||||
function makeThunk(expr, env) { return new Thunk(expr, env); }
|
function makeThunk(expr, env) { return new Thunk(expr, env); }
|
||||||
|
|
||||||
|
function makeSpread(attrs) { return new SxSpread(attrs || {}); }
|
||||||
|
function isSpread(x) { return x != null && x._spread === true; }
|
||||||
|
function spreadAttrs(s) { return s && s._spread ? s.attrs : {}; }
|
||||||
|
|
||||||
|
function sxCollect(bucket, value) {
|
||||||
|
if (!_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
||||||
|
var items = _collectBuckets[bucket];
|
||||||
|
if (items.indexOf(value) === -1) items.push(value);
|
||||||
|
}
|
||||||
|
function sxCollected(bucket) {
|
||||||
|
return _collectBuckets[bucket] ? _collectBuckets[bucket].slice() : [];
|
||||||
|
}
|
||||||
|
function sxClearCollected(bucket) {
|
||||||
|
if (_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function providePush(name, value) {
|
||||||
|
if (!_provideStacks[name]) _provideStacks[name] = [];
|
||||||
|
_provideStacks[name].push({value: value !== undefined ? value : NIL, emitted: []});
|
||||||
|
}
|
||||||
|
function providePop(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) _provideStacks[name].pop();
|
||||||
|
}
|
||||||
|
function sxContext(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].value;
|
||||||
|
}
|
||||||
|
if (arguments.length > 1) return arguments[1];
|
||||||
|
throw new Error("No provider for: " + name);
|
||||||
|
}
|
||||||
|
function sxEmit(name, value) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
_provideStacks[name][_provideStacks[name].length - 1].emitted.push(value);
|
||||||
|
} else {
|
||||||
|
throw new Error("No provider for emit!: " + name);
|
||||||
|
}
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
function sxEmitted(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].emitted.slice();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
function lambdaParams(f) { return f.params; }
|
function lambdaParams(f) { return f.params; }
|
||||||
function lambdaBody(f) { return f.body; }
|
function lambdaBody(f) { return f.body; }
|
||||||
function lambdaClosure(f) { return f.closure; }
|
function lambdaClosure(f) { return f.closure; }
|
||||||
@@ -466,6 +518,21 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// stdlib.spread — spread + collect primitives
|
||||||
|
PRIMITIVES["make-spread"] = makeSpread;
|
||||||
|
PRIMITIVES["spread?"] = isSpread;
|
||||||
|
PRIMITIVES["spread-attrs"] = spreadAttrs;
|
||||||
|
PRIMITIVES["collect!"] = sxCollect;
|
||||||
|
PRIMITIVES["collected"] = sxCollected;
|
||||||
|
PRIMITIVES["clear-collected!"] = sxClearCollected;
|
||||||
|
// provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = providePush;
|
||||||
|
PRIMITIVES["provide-pop!"] = providePop;
|
||||||
|
PRIMITIVES["context"] = sxContext;
|
||||||
|
PRIMITIVES["emit!"] = sxEmit;
|
||||||
|
PRIMITIVES["emitted"] = sxEmitted;
|
||||||
|
|
||||||
|
|
||||||
function isPrimitive(name) { return name in PRIMITIVES; }
|
function isPrimitive(name) { return name in PRIMITIVES; }
|
||||||
function getPrimitive(name) { return PRIMITIVES[name]; }
|
function getPrimitive(name) { return PRIMITIVES[name]; }
|
||||||
|
|
||||||
@@ -729,10 +796,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 == "defisland")) ? sfDefisland(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 == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(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 == "defisland")) ? sfDefisland(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 == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(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 == "provide")) ? sfProvide(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((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))))))))))));
|
})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))))));
|
||||||
})() : evalCall(head, args, env)));
|
})() : evalCall(head, args, env)));
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -1139,6 +1206,18 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
})();
|
})();
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
|
// sf-provide
|
||||||
|
var sfProvide = function(args, env) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var val = trampoline(evalExpr(nth(args, 1), env));
|
||||||
|
var bodyExprs = slice(args, 2);
|
||||||
|
var result = NIL;
|
||||||
|
providePush(name, val);
|
||||||
|
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; result = trampoline(evalExpr(e, env)); } }
|
||||||
|
providePop(name);
|
||||||
|
return result;
|
||||||
|
})(); };
|
||||||
|
|
||||||
// expand-macro
|
// expand-macro
|
||||||
var expandMacro = function(mac, rawArgs, env) { return (function() {
|
var expandMacro = function(mac, rawArgs, env) { return (function() {
|
||||||
var local = envMerge(macroClosure(mac), env);
|
var local = envMerge(macroClosure(mac), env);
|
||||||
@@ -1277,6 +1356,18 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
})());
|
})());
|
||||||
})()); };
|
})()); };
|
||||||
|
|
||||||
|
// merge-spread-attrs
|
||||||
|
var mergeSpreadAttrs = function(target, spreadDict) { return forEach(function(key) { return (function() {
|
||||||
|
var val = dictGet(spreadDict, key);
|
||||||
|
return (isSxTruthy((key == "class")) ? (function() {
|
||||||
|
var existing = dictGet(target, "class");
|
||||||
|
return dictSet(target, "class", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(" ") + String(val)) : val));
|
||||||
|
})() : (isSxTruthy((key == "style")) ? (function() {
|
||||||
|
var existing = dictGet(target, "style");
|
||||||
|
return dictSet(target, "style", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(";") + String(val)) : val));
|
||||||
|
})() : dictSet(target, key, val)));
|
||||||
|
})(); }, keys(spreadDict)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from parser ===
|
// === Transpiled from parser ===
|
||||||
|
|
||||||
@@ -1419,13 +1510,13 @@ continue; } else { return NIL; } } };
|
|||||||
|
|
||||||
// render-to-html
|
// render-to-html
|
||||||
var renderToHtml = function(expr, env) { setRenderActiveB(true);
|
var renderToHtml = function(expr, env) { setRenderActiveB(true);
|
||||||
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); })(); };
|
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); if (_m == "spread") return 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); 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); if (_m == "spread") return 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", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each"];
|
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each", "provide"];
|
||||||
|
|
||||||
// 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); };
|
||||||
@@ -1433,10 +1524,10 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
// render-list-to-html
|
// render-list-to-html
|
||||||
var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() {
|
var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() {
|
||||||
var head = first(expr);
|
var head = first(expr);
|
||||||
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() {
|
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", filter(function(x) { return !isSxTruthy(isSpread(x)); }, map(function(x) { return renderValueToHtml(x, env); }, expr))) : (function() {
|
||||||
var name = symbolName(head);
|
var name = symbolName(head);
|
||||||
var args = rest(expr);
|
var args = rest(expr);
|
||||||
return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
return (isSxTruthy((name == "<>")) ? join("", filter(function(x) { return !isSxTruthy(isSpread(x)); }, map(function(x) { return renderToHtml(x, env); }, args))) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||||
var val = envGet(env, name);
|
var val = envGet(env, name);
|
||||||
return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name)))));
|
return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name)))));
|
||||||
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))));
|
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))));
|
||||||
@@ -1447,25 +1538,45 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() {
|
var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() {
|
||||||
var condVal = trampoline(evalExpr(nth(expr, 1), env));
|
var condVal = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : ""));
|
return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : ""));
|
||||||
})() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr))))) : (isSxTruthy((name == "cond")) ? (function() {
|
})() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : (isSxTruthy((len(expr) == 3)) ? renderToHtml(nth(expr, 2), env) : (function() {
|
||||||
|
var results = map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr)));
|
||||||
|
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, results));
|
||||||
|
})())) : (isSxTruthy((name == "cond")) ? (function() {
|
||||||
var branch = evalCond(rest(expr), env);
|
var branch = evalCond(rest(expr), env);
|
||||||
return (isSxTruthy(branch) ? renderToHtml(branch, env) : "");
|
return (isSxTruthy(branch) ? renderToHtml(branch, env) : "");
|
||||||
})() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() {
|
})() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() {
|
||||||
var local = processBindings(nth(expr, 1), env);
|
var local = processBindings(nth(expr, 1), env);
|
||||||
return join("", map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr))));
|
return (isSxTruthy((len(expr) == 3)) ? renderToHtml(nth(expr, 2), local) : (function() {
|
||||||
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr)))) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() {
|
var results = map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr)));
|
||||||
|
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, results));
|
||||||
|
})());
|
||||||
|
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (isSxTruthy((len(expr) == 2)) ? renderToHtml(nth(expr, 1), env) : (function() {
|
||||||
|
var results = map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr)));
|
||||||
|
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, results));
|
||||||
|
})()) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() {
|
||||||
var f = trampoline(evalExpr(nth(expr, 1), env));
|
var f = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll));
|
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)));
|
||||||
})() : (isSxTruthy((name == "map-indexed")) ? (function() {
|
})() : (isSxTruthy((name == "map-indexed")) ? (function() {
|
||||||
var f = trampoline(evalExpr(nth(expr, 1), env));
|
var f = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
return join("", mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll));
|
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll)));
|
||||||
})() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() {
|
})() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() {
|
||||||
var f = trampoline(evalExpr(nth(expr, 1), env));
|
var f = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll));
|
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)));
|
||||||
})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))))); };
|
})() : (isSxTruthy((name == "provide")) ? (function() {
|
||||||
|
var provName = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
|
var provVal = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
|
var bodyStart = 3;
|
||||||
|
var bodyCount = (len(expr) - 3);
|
||||||
|
providePush(provName, provVal);
|
||||||
|
return (function() {
|
||||||
|
var result = (isSxTruthy((bodyCount == 1)) ? renderToHtml(nth(expr, bodyStart), env) : join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(i) { return renderToHtml(nth(expr, i), env); }, range(bodyStart, (bodyStart + bodyCount))))));
|
||||||
|
providePop(provName);
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))))))); };
|
||||||
|
|
||||||
// render-lambda-html
|
// render-lambda-html
|
||||||
var renderLambdaHtml = function(f, args, env) { return (function() {
|
var renderLambdaHtml = function(f, args, env) { return (function() {
|
||||||
@@ -1490,7 +1601,14 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
var local = envMerge(componentClosure(comp), env);
|
var local = envMerge(componentClosure(comp), env);
|
||||||
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
|
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
|
||||||
if (isSxTruthy(componentHasChildren(comp))) {
|
if (isSxTruthy(componentHasChildren(comp))) {
|
||||||
envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))));
|
(function() {
|
||||||
|
var parts = [];
|
||||||
|
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
|
||||||
|
var r = renderToHtml(c, env);
|
||||||
|
return (isSxTruthy(!isSxTruthy(isSpread(r))) ? append_b(parts, r) : NIL);
|
||||||
|
})(); } }
|
||||||
|
return envSet(local, "children", makeRawHtml(join("", parts)));
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
return renderToHtml(componentBody(comp), local);
|
return renderToHtml(componentBody(comp), local);
|
||||||
})();
|
})();
|
||||||
@@ -1502,7 +1620,14 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
var attrs = first(parsed);
|
var attrs = first(parsed);
|
||||||
var children = nth(parsed, 1);
|
var children = nth(parsed, 1);
|
||||||
var isVoid = contains(VOID_ELEMENTS, tag);
|
var isVoid = contains(VOID_ELEMENTS, tag);
|
||||||
return (String("<") + String(tag) + String(renderAttrs(attrs)) + String((isSxTruthy(isVoid) ? " />" : (String(">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("</") + String(tag) + String(">")))));
|
return (isSxTruthy(isVoid) ? (String("<") + String(tag) + String(renderAttrs(attrs)) + String(" />")) : (function() {
|
||||||
|
var contentParts = [];
|
||||||
|
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
|
||||||
|
var result = renderToHtml(c, env);
|
||||||
|
return (isSxTruthy(isSpread(result)) ? mergeSpreadAttrs(attrs, spreadAttrs(result)) : append_b(contentParts, result));
|
||||||
|
})(); } }
|
||||||
|
return (String("<") + String(tag) + String(renderAttrs(attrs)) + String(">") + String(join("", contentParts)) + String("</") + String(tag) + String(">"));
|
||||||
|
})());
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// render-html-lake
|
// render-html-lake
|
||||||
@@ -1519,7 +1644,15 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||||
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
|
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
|
||||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
return (String("<") + String(lakeTag) + String(" data-sx-lake=\"") + String(escapeAttr(sxOr(lakeId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("</") + String(lakeTag) + String(">"));
|
return (function() {
|
||||||
|
var lakeAttrs = {["data-sx-lake"]: sxOr(lakeId, "")};
|
||||||
|
var contentParts = [];
|
||||||
|
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
|
||||||
|
var result = renderToHtml(c, env);
|
||||||
|
return (isSxTruthy(isSpread(result)) ? mergeSpreadAttrs(lakeAttrs, spreadAttrs(result)) : append_b(contentParts, result));
|
||||||
|
})(); } }
|
||||||
|
return (String("<") + String(lakeTag) + String(renderAttrs(lakeAttrs)) + String(">") + String(join("", contentParts)) + String("</") + String(lakeTag) + String(">"));
|
||||||
|
})();
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// render-html-marsh
|
// render-html-marsh
|
||||||
@@ -1536,7 +1669,15 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||||
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
|
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
|
||||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
return (String("<") + String(marshTag) + String(" data-sx-marsh=\"") + String(escapeAttr(sxOr(marshId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("</") + String(marshTag) + String(">"));
|
return (function() {
|
||||||
|
var marshAttrs = {["data-sx-marsh"]: sxOr(marshId, "")};
|
||||||
|
var contentParts = [];
|
||||||
|
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
|
||||||
|
var result = renderToHtml(c, env);
|
||||||
|
return (isSxTruthy(isSpread(result)) ? mergeSpreadAttrs(marshAttrs, spreadAttrs(result)) : append_b(contentParts, result));
|
||||||
|
})(); } }
|
||||||
|
return (String("<") + String(marshTag) + String(renderAttrs(marshAttrs)) + String(">") + String(join("", contentParts)) + String("</") + String(marshTag) + String(">"));
|
||||||
|
})();
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// render-html-island
|
// render-html-island
|
||||||
@@ -1556,7 +1697,14 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
var islandName = componentName(island);
|
var islandName = componentName(island);
|
||||||
{ var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
|
{ var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
|
||||||
if (isSxTruthy(componentHasChildren(island))) {
|
if (isSxTruthy(componentHasChildren(island))) {
|
||||||
envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))));
|
(function() {
|
||||||
|
var parts = [];
|
||||||
|
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
|
||||||
|
var r = renderToHtml(c, env);
|
||||||
|
return (isSxTruthy(!isSxTruthy(isSpread(r))) ? append_b(parts, r) : NIL);
|
||||||
|
})(); } }
|
||||||
|
return envSet(local, "children", makeRawHtml(join("", parts)));
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
return (function() {
|
return (function() {
|
||||||
var bodyHtml = renderToHtml(componentBody(island), local);
|
var bodyHtml = renderToHtml(componentBody(island), local);
|
||||||
@@ -1583,7 +1731,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
|
return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
|
||||||
var name = symbolName(expr);
|
var name = symbolName(expr);
|
||||||
return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name))))))));
|
return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name))))))));
|
||||||
})(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); return expr; })(); };
|
})(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); if (_m == "spread") return expr; return expr; })(); };
|
||||||
|
|
||||||
// aser-list
|
// aser-list
|
||||||
var aserList = function(expr, env) { return (function() {
|
var aserList = function(expr, env) { return (function() {
|
||||||
@@ -1633,7 +1781,7 @@ return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if
|
|||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// SPECIAL_FORM_NAMES
|
// SPECIAL_FORM_NAMES
|
||||||
var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect"];
|
var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect", "provide"];
|
||||||
|
|
||||||
// HO_FORM_NAMES
|
// HO_FORM_NAMES
|
||||||
var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"];
|
var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"];
|
||||||
@@ -1704,7 +1852,15 @@ return result; }, args);
|
|||||||
return append_b(results, aser(lambdaBody(f), local));
|
return append_b(results, aser(lambdaBody(f), local));
|
||||||
})() : invoke(f, item)); } }
|
})() : invoke(f, item)); } }
|
||||||
return (isSxTruthy(isEmpty(results)) ? NIL : results);
|
return (isSxTruthy(isEmpty(results)) ? NIL : results);
|
||||||
})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env)))))))))))))));
|
})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : (isSxTruthy((name == "provide")) ? (function() {
|
||||||
|
var provName = trampoline(evalExpr(first(args), env));
|
||||||
|
var provVal = trampoline(evalExpr(nth(args, 1), env));
|
||||||
|
var result = NIL;
|
||||||
|
providePush(provName, provVal);
|
||||||
|
{ var _c = slice(args, 2); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } }
|
||||||
|
providePop(provName);
|
||||||
|
return result;
|
||||||
|
})() : trampoline(evalExpr(expr, env))))))))))))))));
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// eval-case-aser
|
// eval-case-aser
|
||||||
@@ -1725,7 +1881,7 @@ return result; }, args);
|
|||||||
|
|
||||||
// render-to-dom
|
// render-to-dom
|
||||||
var renderToDom = function(expr, env, ns) { setRenderActiveB(true);
|
var renderToDom = function(expr, env, ns) { setRenderActiveB(true);
|
||||||
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 (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
|
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 == "spread") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : 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() {
|
||||||
@@ -1773,7 +1929,19 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
|||||||
return (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 (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))) ? (function() {
|
||||||
|
var child = renderToDom(arg, env, newNs);
|
||||||
|
return (isSxTruthy(isSpread(child)) ? forEach(function(key) { return (function() {
|
||||||
|
var val = dictGet(spreadAttrs(child), key);
|
||||||
|
return (isSxTruthy((key == "class")) ? (function() {
|
||||||
|
var existing = domGetAttr(el, "class");
|
||||||
|
return domSetAttr(el, "class", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(" ") + String(val)) : val));
|
||||||
|
})() : (isSxTruthy((key == "style")) ? (function() {
|
||||||
|
var existing = domGetAttr(el, "style");
|
||||||
|
return domSetAttr(el, "style", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(";") + String(val)) : val));
|
||||||
|
})() : domSetAttr(el, key, (String(val)))));
|
||||||
|
})(); }, keys(spreadAttrs(child))) : domAppend(el, child));
|
||||||
|
})() : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
||||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
return el;
|
return el;
|
||||||
})(); };
|
})(); };
|
||||||
@@ -1825,7 +1993,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
|||||||
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", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary"];
|
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "provide"];
|
||||||
|
|
||||||
// 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); };
|
||||||
@@ -1959,7 +2127,15 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
|||||||
return domAppend(frag, val);
|
return domAppend(frag, val);
|
||||||
})(); } }
|
})(); } }
|
||||||
return frag;
|
return frag;
|
||||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); };
|
})() : (isSxTruthy((name == "provide")) ? (function() {
|
||||||
|
var provName = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
|
var provVal = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
|
var frag = createFragment();
|
||||||
|
providePush(provName, provVal);
|
||||||
|
{ var _c = range(3, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
|
||||||
|
providePop(provName);
|
||||||
|
return frag;
|
||||||
|
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))))); };
|
||||||
|
|
||||||
// render-lambda-dom
|
// render-lambda-dom
|
||||||
var renderLambdaDom = function(f, args, env, ns) { return (function() {
|
var renderLambdaDom = function(f, args, env, ns) { return (function() {
|
||||||
@@ -6346,6 +6522,17 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
emitEvent: emitEvent,
|
emitEvent: emitEvent,
|
||||||
onEvent: onEvent,
|
onEvent: onEvent,
|
||||||
bridgeEvent: bridgeEvent,
|
bridgeEvent: bridgeEvent,
|
||||||
|
makeSpread: makeSpread,
|
||||||
|
isSpread: isSpread,
|
||||||
|
spreadAttrs: spreadAttrs,
|
||||||
|
collect: sxCollect,
|
||||||
|
collected: sxCollected,
|
||||||
|
clearCollected: sxClearCollected,
|
||||||
|
providePush: providePush,
|
||||||
|
providePop: providePop,
|
||||||
|
context: sxContext,
|
||||||
|
emit: sxEmit,
|
||||||
|
emitted: sxEmitted,
|
||||||
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"string" (escape-html expr)
|
"string" (escape-html expr)
|
||||||
"number" (escape-html (str expr))
|
"number" (escape-html (str expr))
|
||||||
"raw-html" (raw-html-content expr)
|
"raw-html" (raw-html-content expr)
|
||||||
|
"spread" expr
|
||||||
"symbol" (let ((val (async-eval expr env ctx)))
|
"symbol" (let ((val (async-eval expr env ctx)))
|
||||||
(async-render val env ctx))
|
(async-render val env ctx))
|
||||||
"keyword" (escape-html (keyword-name expr))
|
"keyword" (escape-html (keyword-name expr))
|
||||||
@@ -79,9 +80,10 @@
|
|||||||
(= name "raw!")
|
(= name "raw!")
|
||||||
(async-render-raw args env ctx)
|
(async-render-raw args env ctx)
|
||||||
|
|
||||||
;; Fragment
|
;; Fragment (spreads filtered — no parent element)
|
||||||
(= name "<>")
|
(= name "<>")
|
||||||
(join "" (async-map-render args env ctx))
|
(join "" (filter (fn (r) (not (spread? r)))
|
||||||
|
(async-map-render args env ctx)))
|
||||||
|
|
||||||
;; html: prefix
|
;; html: prefix
|
||||||
(starts-with? name "html:")
|
(starts-with? name "html:")
|
||||||
@@ -167,16 +169,24 @@
|
|||||||
(let ((class-val (dict-get attrs "class")))
|
(let ((class-val (dict-get attrs "class")))
|
||||||
(when (and (not (nil? class-val)) (not (= class-val false)))
|
(when (and (not (nil? class-val)) (not (= class-val false)))
|
||||||
(css-class-collect! (str class-val))))
|
(css-class-collect! (str class-val))))
|
||||||
;; Build opening tag
|
|
||||||
(let ((opening (str "<" tag (render-attrs attrs) ">")))
|
|
||||||
(if (contains? VOID_ELEMENTS tag)
|
(if (contains? VOID_ELEMENTS tag)
|
||||||
opening
|
(str "<" tag (render-attrs attrs) ">")
|
||||||
|
;; Render children, collecting spreads and content separately
|
||||||
(let ((token (if (or (= tag "svg") (= tag "math"))
|
(let ((token (if (or (= tag "svg") (= tag "math"))
|
||||||
(svg-context-set! true)
|
(svg-context-set! true)
|
||||||
nil))
|
nil))
|
||||||
(child-html (join "" (async-map-render children env ctx))))
|
(content-parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((result (async-render c env ctx)))
|
||||||
|
(if (spread? result)
|
||||||
|
(merge-spread-attrs attrs (spread-attrs result))
|
||||||
|
(append! content-parts result))))
|
||||||
|
children)
|
||||||
(when token (svg-context-reset! token))
|
(when token (svg-context-reset! token))
|
||||||
(str opening child-html "</" tag ">")))))))
|
(str "<" tag (render-attrs attrs) ">"
|
||||||
|
(join "" content-parts)
|
||||||
|
"</" tag ">"))))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -221,10 +231,17 @@
|
|||||||
(for-each
|
(for-each
|
||||||
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||||
(component-params comp))
|
(component-params comp))
|
||||||
|
;; Pre-render children to raw HTML (filter spreads — no parent element)
|
||||||
(when (component-has-children? comp)
|
(when (component-has-children? comp)
|
||||||
|
(let ((parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((r (async-render c env ctx)))
|
||||||
|
(when (not (spread? r))
|
||||||
|
(append! parts r))))
|
||||||
|
children)
|
||||||
(env-set! local "children"
|
(env-set! local "children"
|
||||||
(make-raw-html
|
(make-raw-html (join "" parts)))))
|
||||||
(join "" (async-map-render children env ctx)))))
|
|
||||||
(async-render (component-body comp) local ctx)))))
|
(async-render (component-body comp) local ctx)))))
|
||||||
|
|
||||||
|
|
||||||
@@ -242,10 +259,17 @@
|
|||||||
(for-each
|
(for-each
|
||||||
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||||
(component-params island))
|
(component-params island))
|
||||||
|
;; Pre-render children (filter spreads — no parent element)
|
||||||
(when (component-has-children? island)
|
(when (component-has-children? island)
|
||||||
|
(let ((parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((r (async-render c env ctx)))
|
||||||
|
(when (not (spread? r))
|
||||||
|
(append! parts r))))
|
||||||
|
children)
|
||||||
(env-set! local "children"
|
(env-set! local "children"
|
||||||
(make-raw-html
|
(make-raw-html (join "" parts)))))
|
||||||
(join "" (async-map-render children env ctx)))))
|
|
||||||
(let ((body-html (async-render (component-body island) local ctx))
|
(let ((body-html (async-render (component-body island) local ctx))
|
||||||
(state-json (serialize-island-state kwargs)))
|
(state-json (serialize-island-state kwargs)))
|
||||||
(str "<span data-sx-island=\"" (escape-attr island-name) "\""
|
(str "<span data-sx-island=\"" (escape-attr island-name) "\""
|
||||||
@@ -317,7 +341,7 @@
|
|||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
||||||
"deftype" "defeffect"
|
"deftype" "defeffect"
|
||||||
"map" "map-indexed" "filter" "for-each"))
|
"map" "map-indexed" "filter" "for-each" "provide"))
|
||||||
|
|
||||||
(define async-render-form? :effects []
|
(define async-render-form? :effects []
|
||||||
(fn ((name :as string))
|
(fn ((name :as string))
|
||||||
@@ -343,11 +367,14 @@
|
|||||||
(async-render (nth expr 3) env ctx)
|
(async-render (nth expr 3) env ctx)
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
;; when
|
;; when — single body: pass through (spread propagates). Multi: join strings.
|
||||||
(= name "when")
|
(= name "when")
|
||||||
(if (not (async-eval (nth expr 1) env ctx))
|
(if (not (async-eval (nth expr 1) env ctx))
|
||||||
""
|
""
|
||||||
(join "" (async-map-render (slice expr 2) env ctx)))
|
(if (= (len expr) 3)
|
||||||
|
(async-render (nth expr 2) env ctx)
|
||||||
|
(let ((results (async-map-render (slice expr 2) env ctx)))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||||
|
|
||||||
;; cond — uses cond-scheme? (every? check) from eval.sx
|
;; cond — uses cond-scheme? (every? check) from eval.sx
|
||||||
(= name "cond")
|
(= name "cond")
|
||||||
@@ -360,43 +387,66 @@
|
|||||||
(= name "case")
|
(= name "case")
|
||||||
(async-render (async-eval expr env ctx) env ctx)
|
(async-render (async-eval expr env ctx) env ctx)
|
||||||
|
|
||||||
;; let / let*
|
;; let / let* — single body: pass through. Multi: join strings.
|
||||||
(or (= name "let") (= name "let*"))
|
(or (= name "let") (= name "let*"))
|
||||||
(let ((local (async-process-bindings (nth expr 1) env ctx)))
|
(let ((local (async-process-bindings (nth expr 1) env ctx)))
|
||||||
(join "" (async-map-render (slice expr 2) local ctx)))
|
(if (= (len expr) 3)
|
||||||
|
(async-render (nth expr 2) local ctx)
|
||||||
|
(let ((results (async-map-render (slice expr 2) local ctx)))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||||
|
|
||||||
;; begin / do
|
;; begin / do — single body: pass through. Multi: join strings.
|
||||||
(or (= name "begin") (= name "do"))
|
(or (= name "begin") (= name "do"))
|
||||||
(join "" (async-map-render (rest expr) env ctx))
|
(if (= (len expr) 2)
|
||||||
|
(async-render (nth expr 1) env ctx)
|
||||||
|
(let ((results (async-map-render (rest expr) env ctx)))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results))))
|
||||||
|
|
||||||
;; Definition forms
|
;; Definition forms
|
||||||
(definition-form? name)
|
(definition-form? name)
|
||||||
(do (async-eval expr env ctx) "")
|
(do (async-eval expr env ctx) "")
|
||||||
|
|
||||||
;; map
|
;; map — spreads filtered
|
||||||
(= name "map")
|
(= name "map")
|
||||||
(let ((f (async-eval (nth expr 1) env ctx))
|
(let ((f (async-eval (nth expr 1) env ctx))
|
||||||
(coll (async-eval (nth expr 2) env ctx)))
|
(coll (async-eval (nth expr 2) env ctx)))
|
||||||
(join ""
|
(join ""
|
||||||
(async-map-fn-render f coll env ctx)))
|
(filter (fn (r) (not (spread? r)))
|
||||||
|
(async-map-fn-render f coll env ctx))))
|
||||||
|
|
||||||
;; map-indexed
|
;; map-indexed — spreads filtered
|
||||||
(= name "map-indexed")
|
(= name "map-indexed")
|
||||||
(let ((f (async-eval (nth expr 1) env ctx))
|
(let ((f (async-eval (nth expr 1) env ctx))
|
||||||
(coll (async-eval (nth expr 2) env ctx)))
|
(coll (async-eval (nth expr 2) env ctx)))
|
||||||
(join ""
|
(join ""
|
||||||
(async-map-indexed-fn-render f coll env ctx)))
|
(filter (fn (r) (not (spread? r)))
|
||||||
|
(async-map-indexed-fn-render f coll env ctx))))
|
||||||
|
|
||||||
;; filter — eval fully then render
|
;; filter — eval fully then render
|
||||||
(= name "filter")
|
(= name "filter")
|
||||||
(async-render (async-eval expr env ctx) env ctx)
|
(async-render (async-eval expr env ctx) env ctx)
|
||||||
|
|
||||||
;; for-each (render variant)
|
;; for-each (render variant) — spreads filtered
|
||||||
(= name "for-each")
|
(= name "for-each")
|
||||||
(let ((f (async-eval (nth expr 1) env ctx))
|
(let ((f (async-eval (nth expr 1) env ctx))
|
||||||
(coll (async-eval (nth expr 2) env ctx)))
|
(coll (async-eval (nth expr 2) env ctx)))
|
||||||
(join ""
|
(join ""
|
||||||
(async-map-fn-render f coll env ctx)))
|
(filter (fn (r) (not (spread? r)))
|
||||||
|
(async-map-fn-render f coll env ctx))))
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (async-eval (nth expr 1) env ctx))
|
||||||
|
(prov-val (async-eval (nth expr 2) env ctx))
|
||||||
|
(body-start 3)
|
||||||
|
(body-count (- (len expr) 3)))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(let ((result (if (= body-count 1)
|
||||||
|
(async-render (nth expr body-start) env ctx)
|
||||||
|
(let ((results (async-map-render (slice expr body-start) env ctx)))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results))))))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result))
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
@@ -565,6 +615,9 @@
|
|||||||
|
|
||||||
"dict" (async-aser-dict expr env ctx)
|
"dict" (async-aser-dict expr env ctx)
|
||||||
|
|
||||||
|
;; Spread — pass through for client rendering
|
||||||
|
"spread" expr
|
||||||
|
|
||||||
"list"
|
"list"
|
||||||
(if (empty? expr)
|
(if (empty? expr)
|
||||||
(list)
|
(list)
|
||||||
@@ -855,7 +908,7 @@
|
|||||||
"define" "defcomp" "defmacro" "defstyle"
|
"define" "defcomp" "defmacro" "defstyle"
|
||||||
"defhandler" "defpage" "defquery" "defaction"
|
"defhandler" "defpage" "defquery" "defaction"
|
||||||
"begin" "do" "quote" "->" "set!" "defisland"
|
"begin" "do" "quote" "->" "set!" "defisland"
|
||||||
"deftype" "defeffect"))
|
"deftype" "defeffect" "provide"))
|
||||||
|
|
||||||
(define ASYNC_ASER_HO_NAMES
|
(define ASYNC_ASER_HO_NAMES
|
||||||
(list "map" "map-indexed" "filter" "for-each"))
|
(list "map" "map-indexed" "filter" "for-each"))
|
||||||
@@ -993,6 +1046,17 @@
|
|||||||
(= name "deftype") (= name "defeffect"))
|
(= name "deftype") (= name "defeffect"))
|
||||||
(do (async-eval expr env ctx) nil)
|
(do (async-eval expr env ctx) nil)
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (async-eval (first args) env ctx))
|
||||||
|
(prov-val (async-eval (nth args 1) env ctx))
|
||||||
|
(result nil))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(for-each (fn (body) (set! result (async-aser body env ctx)))
|
||||||
|
(slice args 2))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result)
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
(async-eval expr env ctx)))))
|
(async-eval expr env ctx)))))
|
||||||
@@ -1250,6 +1314,14 @@
|
|||||||
;; (svg-context-reset! token) — reset SVG context
|
;; (svg-context-reset! token) — reset SVG context
|
||||||
;; (css-class-collect! val) — collect CSS classes
|
;; (css-class-collect! val) — collect CSS classes
|
||||||
;;
|
;;
|
||||||
|
;; Spread + collect (from render.sx):
|
||||||
|
;; (spread? x) — check if spread value
|
||||||
|
;; (spread-attrs s) — extract attrs dict from spread
|
||||||
|
;; (merge-spread-attrs tgt src) — merge spread attrs onto target
|
||||||
|
;; (collect! bucket value) — add to render-time accumulator
|
||||||
|
;; (collected bucket) — read render-time accumulator
|
||||||
|
;; (clear-collected! bucket) — clear accumulator
|
||||||
|
;;
|
||||||
;; Raw HTML:
|
;; Raw HTML:
|
||||||
;; (is-raw-html? x) — check if raw HTML marker
|
;; (is-raw-html? x) — check if raw HTML marker
|
||||||
;; (make-raw-html s) — wrap string as raw HTML
|
;; (make-raw-html s) — wrap string as raw HTML
|
||||||
|
|||||||
@@ -44,6 +44,9 @@
|
|||||||
;; Pre-rendered DOM node → pass through
|
;; Pre-rendered DOM node → pass through
|
||||||
"dom-node" expr
|
"dom-node" expr
|
||||||
|
|
||||||
|
;; Spread → pass through (parent element handles it)
|
||||||
|
"spread" expr
|
||||||
|
|
||||||
;; Dict → empty
|
;; Dict → empty
|
||||||
"dict" (create-fragment)
|
"dict" (create-fragment)
|
||||||
|
|
||||||
@@ -221,10 +224,34 @@
|
|||||||
(dom-set-attr el attr-name (str attr-val)))))
|
(dom-set-attr el attr-name (str attr-val)))))
|
||||||
(assoc state "skip" true "i" (inc (get state "i"))))
|
(assoc state "skip" true "i" (inc (get state "i"))))
|
||||||
|
|
||||||
;; Positional arg → child
|
;; Positional arg → child (or spread → merge attrs onto element)
|
||||||
(do
|
(do
|
||||||
(when (not (contains? VOID_ELEMENTS tag))
|
(when (not (contains? VOID_ELEMENTS tag))
|
||||||
(dom-append el (render-to-dom arg env new-ns)))
|
(let ((child (render-to-dom arg env new-ns)))
|
||||||
|
(if (spread? child)
|
||||||
|
;; Spread: merge attrs onto parent element
|
||||||
|
(for-each
|
||||||
|
(fn ((key :as string))
|
||||||
|
(let ((val (dict-get (spread-attrs child) key)))
|
||||||
|
(if (= key "class")
|
||||||
|
;; Class: append to existing
|
||||||
|
(let ((existing (dom-get-attr el "class")))
|
||||||
|
(dom-set-attr el "class"
|
||||||
|
(if (and existing (not (= existing "")))
|
||||||
|
(str existing " " val)
|
||||||
|
val)))
|
||||||
|
(if (= key "style")
|
||||||
|
;; Style: append with semicolon
|
||||||
|
(let ((existing (dom-get-attr el "style")))
|
||||||
|
(dom-set-attr el "style"
|
||||||
|
(if (and existing (not (= existing "")))
|
||||||
|
(str existing ";" val)
|
||||||
|
val)))
|
||||||
|
;; Other attrs: overwrite
|
||||||
|
(dom-set-attr el key (str val))))))
|
||||||
|
(keys (spread-attrs child)))
|
||||||
|
;; Normal child: append to element
|
||||||
|
(dom-append el child))))
|
||||||
(assoc state "i" (inc (get state "i"))))))))
|
(assoc state "i" (inc (get state "i"))))))))
|
||||||
(dict "i" 0 "skip" false)
|
(dict "i" 0 "skip" false)
|
||||||
args)
|
args)
|
||||||
@@ -332,7 +359,7 @@
|
|||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
||||||
"map" "map-indexed" "filter" "for-each" "portal"
|
"map" "map-indexed" "filter" "for-each" "portal"
|
||||||
"error-boundary"))
|
"error-boundary" "provide"))
|
||||||
|
|
||||||
(define render-dom-form? :effects []
|
(define render-dom-form? :effects []
|
||||||
(fn ((name :as string))
|
(fn ((name :as string))
|
||||||
@@ -571,6 +598,19 @@
|
|||||||
coll)
|
coll)
|
||||||
frag)
|
frag)
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (trampoline (eval-expr (nth expr 1) env)))
|
||||||
|
(prov-val (trampoline (eval-expr (nth expr 2) env)))
|
||||||
|
(frag (create-fragment)))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(for-each
|
||||||
|
(fn (i)
|
||||||
|
(dom-append frag (render-to-dom (nth expr i) env ns)))
|
||||||
|
(range 3 (len expr)))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
frag)
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
(render-to-dom (trampoline (eval-expr expr env)) env ns))))
|
(render-to-dom (trampoline (eval-expr expr env)) env ns))))
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
"keyword" (escape-html (keyword-name expr))
|
"keyword" (escape-html (keyword-name expr))
|
||||||
;; Raw HTML passthrough
|
;; Raw HTML passthrough
|
||||||
"raw-html" (raw-html-content expr)
|
"raw-html" (raw-html-content expr)
|
||||||
|
;; Spread — pass through as-is (parent element will merge attrs)
|
||||||
|
"spread" expr
|
||||||
;; Everything else — evaluate first
|
;; Everything else — evaluate first
|
||||||
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
|
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@
|
|||||||
"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)
|
||||||
|
"spread" val
|
||||||
:else (escape-html (str val)))))
|
:else (escape-html (str val)))))
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@
|
|||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
||||||
"deftype" "defeffect"
|
"deftype" "defeffect"
|
||||||
"map" "map-indexed" "filter" "for-each"))
|
"map" "map-indexed" "filter" "for-each" "provide"))
|
||||||
|
|
||||||
(define render-html-form? :effects []
|
(define render-html-form? :effects []
|
||||||
(fn ((name :as string))
|
(fn ((name :as string))
|
||||||
@@ -70,14 +73,16 @@
|
|||||||
""
|
""
|
||||||
(let ((head (first expr)))
|
(let ((head (first expr)))
|
||||||
(if (not (= (type-of head) "symbol"))
|
(if (not (= (type-of head) "symbol"))
|
||||||
;; Data list — render each item
|
;; Data list — render each item (spreads filtered — no parent element)
|
||||||
(join "" (map (fn (x) (render-value-to-html x env)) expr))
|
(join "" (filter (fn (x) (not (spread? x)))
|
||||||
|
(map (fn (x) (render-value-to-html x env)) expr)))
|
||||||
(let ((name (symbol-name head))
|
(let ((name (symbol-name head))
|
||||||
(args (rest expr)))
|
(args (rest expr)))
|
||||||
(cond
|
(cond
|
||||||
;; Fragment
|
;; Fragment (spreads filtered — no parent element)
|
||||||
(= name "<>")
|
(= name "<>")
|
||||||
(join "" (map (fn (x) (render-to-html x env)) args))
|
(join "" (filter (fn (x) (not (spread? x)))
|
||||||
|
(map (fn (x) (render-to-html x env)) args)))
|
||||||
|
|
||||||
;; Raw HTML passthrough
|
;; Raw HTML passthrough
|
||||||
(= name "raw!")
|
(= name "raw!")
|
||||||
@@ -147,14 +152,15 @@
|
|||||||
(render-to-html (nth expr 3) env)
|
(render-to-html (nth expr 3) env)
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
;; when
|
;; when — single body: pass through (spread propagates). Multi: join strings.
|
||||||
(= name "when")
|
(= name "when")
|
||||||
(if (not (trampoline (eval-expr (nth expr 1) env)))
|
(if (not (trampoline (eval-expr (nth expr 1) env)))
|
||||||
""
|
""
|
||||||
(join ""
|
(if (= (len expr) 3)
|
||||||
(map
|
(render-to-html (nth expr 2) env)
|
||||||
(fn (i) (render-to-html (nth expr i) env))
|
(let ((results (map (fn (i) (render-to-html (nth expr i) env))
|
||||||
(range 2 (len expr)))))
|
(range 2 (len expr)))))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||||
|
|
||||||
;; cond
|
;; cond
|
||||||
(= name "cond")
|
(= name "cond")
|
||||||
@@ -167,64 +173,84 @@
|
|||||||
(= name "case")
|
(= name "case")
|
||||||
(render-to-html (trampoline (eval-expr expr env)) env)
|
(render-to-html (trampoline (eval-expr expr env)) env)
|
||||||
|
|
||||||
;; let / let*
|
;; let / let* — single body: pass through. Multi: join strings.
|
||||||
(or (= name "let") (= name "let*"))
|
(or (= name "let") (= name "let*"))
|
||||||
(let ((local (process-bindings (nth expr 1) env)))
|
(let ((local (process-bindings (nth expr 1) env)))
|
||||||
(join ""
|
(if (= (len expr) 3)
|
||||||
(map
|
(render-to-html (nth expr 2) local)
|
||||||
(fn (i) (render-to-html (nth expr i) local))
|
(let ((results (map (fn (i) (render-to-html (nth expr i) local))
|
||||||
(range 2 (len expr)))))
|
(range 2 (len expr)))))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||||
|
|
||||||
;; begin / do
|
;; begin / do — single body: pass through. Multi: join strings.
|
||||||
(or (= name "begin") (= name "do"))
|
(or (= name "begin") (= name "do"))
|
||||||
(join ""
|
(if (= (len expr) 2)
|
||||||
(map
|
(render-to-html (nth expr 1) env)
|
||||||
(fn (i) (render-to-html (nth expr i) env))
|
(let ((results (map (fn (i) (render-to-html (nth expr i) env))
|
||||||
(range 1 (len expr))))
|
(range 1 (len expr)))))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results))))
|
||||||
|
|
||||||
;; Definition forms — eval for side effects
|
;; Definition forms — eval for side effects
|
||||||
(definition-form? name)
|
(definition-form? name)
|
||||||
(do (trampoline (eval-expr expr env)) "")
|
(do (trampoline (eval-expr expr env)) "")
|
||||||
|
|
||||||
;; map
|
;; map — spreads filtered (no parent element in list context)
|
||||||
(= name "map")
|
(= name "map")
|
||||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||||
(coll (trampoline (eval-expr (nth expr 2) env))))
|
(coll (trampoline (eval-expr (nth expr 2) env))))
|
||||||
(join ""
|
(join ""
|
||||||
|
(filter (fn (r) (not (spread? r)))
|
||||||
(map
|
(map
|
||||||
(fn (item)
|
(fn (item)
|
||||||
(if (lambda? f)
|
(if (lambda? f)
|
||||||
(render-lambda-html f (list item) env)
|
(render-lambda-html f (list item) env)
|
||||||
(render-to-html (apply f (list item)) env)))
|
(render-to-html (apply f (list item)) env)))
|
||||||
coll)))
|
coll))))
|
||||||
|
|
||||||
;; map-indexed
|
;; map-indexed — spreads filtered
|
||||||
(= name "map-indexed")
|
(= name "map-indexed")
|
||||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||||
(coll (trampoline (eval-expr (nth expr 2) env))))
|
(coll (trampoline (eval-expr (nth expr 2) env))))
|
||||||
(join ""
|
(join ""
|
||||||
|
(filter (fn (r) (not (spread? r)))
|
||||||
(map-indexed
|
(map-indexed
|
||||||
(fn (i item)
|
(fn (i item)
|
||||||
(if (lambda? f)
|
(if (lambda? f)
|
||||||
(render-lambda-html f (list i item) env)
|
(render-lambda-html f (list i item) env)
|
||||||
(render-to-html (apply f (list i item)) env)))
|
(render-to-html (apply f (list i item)) env)))
|
||||||
coll)))
|
coll))))
|
||||||
|
|
||||||
;; filter — evaluate fully then render
|
;; filter — evaluate fully then render
|
||||||
(= name "filter")
|
(= name "filter")
|
||||||
(render-to-html (trampoline (eval-expr expr env)) env)
|
(render-to-html (trampoline (eval-expr expr env)) env)
|
||||||
|
|
||||||
;; for-each (render variant)
|
;; for-each (render variant) — spreads filtered
|
||||||
(= name "for-each")
|
(= name "for-each")
|
||||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||||
(coll (trampoline (eval-expr (nth expr 2) env))))
|
(coll (trampoline (eval-expr (nth expr 2) env))))
|
||||||
(join ""
|
(join ""
|
||||||
|
(filter (fn (r) (not (spread? r)))
|
||||||
(map
|
(map
|
||||||
(fn (item)
|
(fn (item)
|
||||||
(if (lambda? f)
|
(if (lambda? f)
|
||||||
(render-lambda-html f (list item) env)
|
(render-lambda-html f (list item) env)
|
||||||
(render-to-html (apply f (list item)) env)))
|
(render-to-html (apply f (list item)) env)))
|
||||||
coll)))
|
coll))))
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (trampoline (eval-expr (nth expr 1) env)))
|
||||||
|
(prov-val (trampoline (eval-expr (nth expr 2) env)))
|
||||||
|
(body-start 3)
|
||||||
|
(body-count (- (len expr) 3)))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(let ((result (if (= body-count 1)
|
||||||
|
(render-to-html (nth expr body-start) env)
|
||||||
|
(join "" (filter (fn (r) (not (spread? r)))
|
||||||
|
(map (fn (i) (render-to-html (nth expr i) env))
|
||||||
|
(range body-start (+ body-start body-count))))))))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result))
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
@@ -281,10 +307,17 @@
|
|||||||
(env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
(env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||||
(component-params comp))
|
(component-params comp))
|
||||||
;; If component accepts children, pre-render them to raw HTML
|
;; If component accepts children, pre-render them to raw HTML
|
||||||
|
;; Spread values are filtered out (no parent element to merge onto)
|
||||||
(when (component-has-children? comp)
|
(when (component-has-children? comp)
|
||||||
|
(let ((parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((r (render-to-html c env)))
|
||||||
|
(when (not (spread? r))
|
||||||
|
(append! parts r))))
|
||||||
|
children)
|
||||||
(env-set! local "children"
|
(env-set! local "children"
|
||||||
(make-raw-html
|
(make-raw-html (join "" parts)))))
|
||||||
(join "" (map (fn (c) (render-to-html c env)) children)))))
|
|
||||||
(render-to-html (component-body comp) local)))))
|
(render-to-html (component-body comp) local)))))
|
||||||
|
|
||||||
|
|
||||||
@@ -294,12 +327,19 @@
|
|||||||
(attrs (first parsed))
|
(attrs (first parsed))
|
||||||
(children (nth parsed 1))
|
(children (nth parsed 1))
|
||||||
(is-void (contains? VOID_ELEMENTS tag)))
|
(is-void (contains? VOID_ELEMENTS tag)))
|
||||||
(str "<" tag
|
|
||||||
(render-attrs attrs)
|
|
||||||
(if is-void
|
(if is-void
|
||||||
" />"
|
(str "<" tag (render-attrs attrs) " />")
|
||||||
(str ">"
|
;; Render children, collecting spreads and content separately
|
||||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
(let ((content-parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((result (render-to-html c env)))
|
||||||
|
(if (spread? result)
|
||||||
|
(merge-spread-attrs attrs (spread-attrs result))
|
||||||
|
(append! content-parts result))))
|
||||||
|
children)
|
||||||
|
(str "<" tag (render-attrs attrs) ">"
|
||||||
|
(join "" content-parts)
|
||||||
"</" tag ">"))))))
|
"</" tag ">"))))))
|
||||||
|
|
||||||
|
|
||||||
@@ -335,9 +375,19 @@
|
|||||||
(assoc state "i" (inc (get state "i"))))))))
|
(assoc state "i" (inc (get state "i"))))))))
|
||||||
(dict "i" 0 "skip" false)
|
(dict "i" 0 "skip" false)
|
||||||
args)
|
args)
|
||||||
(str "<" lake-tag " data-sx-lake=\"" (escape-attr (or lake-id "")) "\">"
|
;; Render children, handling spreads
|
||||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
(let ((lake-attrs (dict "data-sx-lake" (or lake-id "")))
|
||||||
"</" lake-tag ">"))))
|
(content-parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((result (render-to-html c env)))
|
||||||
|
(if (spread? result)
|
||||||
|
(merge-spread-attrs lake-attrs (spread-attrs result))
|
||||||
|
(append! content-parts result))))
|
||||||
|
children)
|
||||||
|
(str "<" lake-tag (render-attrs lake-attrs) ">"
|
||||||
|
(join "" content-parts)
|
||||||
|
"</" lake-tag ">")))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -375,9 +425,19 @@
|
|||||||
(assoc state "i" (inc (get state "i"))))))))
|
(assoc state "i" (inc (get state "i"))))))))
|
||||||
(dict "i" 0 "skip" false)
|
(dict "i" 0 "skip" false)
|
||||||
args)
|
args)
|
||||||
(str "<" marsh-tag " data-sx-marsh=\"" (escape-attr (or marsh-id "")) "\">"
|
;; Render children, handling spreads
|
||||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
(let ((marsh-attrs (dict "data-sx-marsh" (or marsh-id "")))
|
||||||
"</" marsh-tag ">"))))
|
(content-parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((result (render-to-html c env)))
|
||||||
|
(if (spread? result)
|
||||||
|
(merge-spread-attrs marsh-attrs (spread-attrs result))
|
||||||
|
(append! content-parts result))))
|
||||||
|
children)
|
||||||
|
(str "<" marsh-tag (render-attrs marsh-attrs) ">"
|
||||||
|
(join "" content-parts)
|
||||||
|
"</" marsh-tag ">")))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -427,10 +487,17 @@
|
|||||||
(component-params island))
|
(component-params island))
|
||||||
|
|
||||||
;; If island accepts children, pre-render them to raw HTML
|
;; If island accepts children, pre-render them to raw HTML
|
||||||
|
;; Spread values filtered out (no parent element)
|
||||||
(when (component-has-children? island)
|
(when (component-has-children? island)
|
||||||
|
(let ((parts (list)))
|
||||||
|
(for-each
|
||||||
|
(fn (c)
|
||||||
|
(let ((r (render-to-html c env)))
|
||||||
|
(when (not (spread? r))
|
||||||
|
(append! parts r))))
|
||||||
|
children)
|
||||||
(env-set! local "children"
|
(env-set! local "children"
|
||||||
(make-raw-html
|
(make-raw-html (join "" parts)))))
|
||||||
(join "" (map (fn (c) (render-to-html c env)) children)))))
|
|
||||||
|
|
||||||
;; Render the island body as HTML
|
;; Render the island body as HTML
|
||||||
(let ((body-html (render-to-html (component-body island) local))
|
(let ((body-html (render-to-html (component-body island) local))
|
||||||
|
|||||||
@@ -48,6 +48,9 @@
|
|||||||
(list)
|
(list)
|
||||||
(aser-list expr env))
|
(aser-list expr env))
|
||||||
|
|
||||||
|
;; Spread — pass through for client rendering
|
||||||
|
"spread" expr
|
||||||
|
|
||||||
:else expr)))
|
:else expr)))
|
||||||
|
|
||||||
|
|
||||||
@@ -171,7 +174,7 @@
|
|||||||
"defhandler" "defpage" "defquery" "defaction" "defrelation"
|
"defhandler" "defpage" "defquery" "defaction" "defrelation"
|
||||||
"begin" "do" "quote" "quasiquote"
|
"begin" "do" "quote" "quasiquote"
|
||||||
"->" "set!" "letrec" "dynamic-wind" "defisland"
|
"->" "set!" "letrec" "dynamic-wind" "defisland"
|
||||||
"deftype" "defeffect"))
|
"deftype" "defeffect" "provide"))
|
||||||
|
|
||||||
(define HO_FORM_NAMES
|
(define HO_FORM_NAMES
|
||||||
(list "map" "map-indexed" "filter" "reduce"
|
(list "map" "map-indexed" "filter" "reduce"
|
||||||
@@ -309,6 +312,17 @@
|
|||||||
(= name "deftype") (= name "defeffect"))
|
(= name "deftype") (= name "defeffect"))
|
||||||
(do (trampoline (eval-expr expr env)) nil)
|
(do (trampoline (eval-expr expr env)) nil)
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (trampoline (eval-expr (first args) env)))
|
||||||
|
(prov-val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(result nil))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(for-each (fn (body) (set! result (aser body env)))
|
||||||
|
(slice args 2))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result)
|
||||||
|
|
||||||
;; Everything else — evaluate normally
|
;; Everything else — evaluate normally
|
||||||
:else
|
:else
|
||||||
(trampoline (eval-expr expr env))))))
|
(trampoline (eval-expr expr env))))))
|
||||||
|
|||||||
@@ -285,6 +285,19 @@ class PyEmitter:
|
|||||||
"svg-context-set!": "svg_context_set",
|
"svg-context-set!": "svg_context_set",
|
||||||
"svg-context-reset!": "svg_context_reset",
|
"svg-context-reset!": "svg_context_reset",
|
||||||
"css-class-collect!": "css_class_collect",
|
"css-class-collect!": "css_class_collect",
|
||||||
|
# spread + collect primitives
|
||||||
|
"make-spread": "make_spread",
|
||||||
|
"spread?": "is_spread",
|
||||||
|
"spread-attrs": "spread_attrs",
|
||||||
|
"merge-spread-attrs": "merge_spread_attrs",
|
||||||
|
"collect!": "sx_collect",
|
||||||
|
"collected": "sx_collected",
|
||||||
|
"clear-collected!": "sx_clear_collected",
|
||||||
|
"provide-push!": "provide_push",
|
||||||
|
"provide-pop!": "provide_pop",
|
||||||
|
"context": "sx_context",
|
||||||
|
"emit!": "sx_emit",
|
||||||
|
"emitted": "sx_emitted",
|
||||||
"is-raw-html?": "is_raw_html",
|
"is-raw-html?": "is_raw_html",
|
||||||
"async-coroutine?": "is_async_coroutine",
|
"async-coroutine?": "is_async_coroutine",
|
||||||
"async-await!": "async_await",
|
"async-await!": "async_await",
|
||||||
|
|||||||
@@ -313,3 +313,109 @@
|
|||||||
:effects [mutation]
|
:effects [mutation]
|
||||||
:doc "Group multiple signal writes. Subscribers are notified once at the end,
|
:doc "Group multiple signal writes. Subscribers are notified once at the end,
|
||||||
after all values have been updated.")
|
after all values have been updated.")
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Tier 4: Spread + Collect — render-time attribute injection and accumulation
|
||||||
|
;;
|
||||||
|
;; `spread` is a new type: a dict of attributes that, when returned as a child
|
||||||
|
;; of an HTML element, merges its attrs onto the parent element rather than
|
||||||
|
;; rendering as content. This enables components like `~cssx/tw` to inject
|
||||||
|
;; classes and styles onto their parent from inside the child list.
|
||||||
|
;;
|
||||||
|
;; `collect!` / `collected` are render-time accumulators. Values are collected
|
||||||
|
;; into named buckets (with deduplication) during rendering and retrieved at
|
||||||
|
;; flush points (e.g. a single <style> tag for all collected CSS rules).
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(declare-tier :spread :source "render.sx")
|
||||||
|
|
||||||
|
(declare-spread-primitive "make-spread"
|
||||||
|
:params (attrs)
|
||||||
|
:returns "spread"
|
||||||
|
:effects []
|
||||||
|
:doc "Create a spread value from an attrs dict. When this value appears as
|
||||||
|
a child of an HTML element, its attrs are merged onto the parent
|
||||||
|
element (class values joined, others overwritten).")
|
||||||
|
|
||||||
|
(declare-spread-primitive "spread?"
|
||||||
|
:params (x)
|
||||||
|
:returns "boolean"
|
||||||
|
:effects []
|
||||||
|
:doc "Test whether a value is a spread.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "spread-attrs"
|
||||||
|
:params (s)
|
||||||
|
:returns "dict"
|
||||||
|
:effects []
|
||||||
|
:doc "Extract the attrs dict from a spread value.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "collect!"
|
||||||
|
:params (bucket value)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Add value to a named render-time accumulator bucket. Values are
|
||||||
|
deduplicated (no duplicates added). Buckets persist for the duration
|
||||||
|
of the current render pass.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "collected"
|
||||||
|
:params (bucket)
|
||||||
|
:returns "list"
|
||||||
|
:effects []
|
||||||
|
:doc "Return all values collected in the named bucket during the current
|
||||||
|
render pass. Returns an empty list if the bucket doesn't exist.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "clear-collected!"
|
||||||
|
:params (bucket)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Clear a named render-time accumulator bucket. Used at flush points
|
||||||
|
after emitting collected values (e.g. after writing a <style> tag).")
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Tier 5: Dynamic scope — render-time provide/context/emit!
|
||||||
|
;;
|
||||||
|
;; `provide` is a special form (not a primitive) that creates a named scope
|
||||||
|
;; with a value and an empty accumulator. `context` reads the value from the
|
||||||
|
;; nearest enclosing provider. `emit!` appends to the accumulator, `emitted`
|
||||||
|
;; reads the accumulated values.
|
||||||
|
;;
|
||||||
|
;; The platform must implement per-name stacks. Each entry has a value and
|
||||||
|
;; an emitted list. `provide-push!`/`provide-pop!` manage the stack.
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(declare-tier :dynamic-scope :source "eval.sx")
|
||||||
|
|
||||||
|
(declare-spread-primitive "provide-push!"
|
||||||
|
:params (name value)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Push a provider scope with name and value (platform internal).")
|
||||||
|
|
||||||
|
(declare-spread-primitive "provide-pop!"
|
||||||
|
:params (name)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Pop the most recent provider scope for name (platform internal).")
|
||||||
|
|
||||||
|
(declare-spread-primitive "context"
|
||||||
|
:params (name &rest default)
|
||||||
|
:returns "any"
|
||||||
|
:effects []
|
||||||
|
:doc "Read value from nearest enclosing provide with matching name.
|
||||||
|
Errors if no provider and no default given.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "emit!"
|
||||||
|
:params (name value)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Append value to nearest enclosing provide's accumulator.
|
||||||
|
Errors if no matching provider. No deduplication.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "emitted"
|
||||||
|
:params (name)
|
||||||
|
:returns "list"
|
||||||
|
:effects []
|
||||||
|
:doc "Return list of values emitted into nearest matching provider.
|
||||||
|
Empty list if no provider.")
|
||||||
|
|||||||
@@ -162,6 +162,7 @@
|
|||||||
(= name "reset") (sf-reset args env)
|
(= name "reset") (sf-reset args env)
|
||||||
(= name "shift") (sf-shift args env)
|
(= name "shift") (sf-shift args env)
|
||||||
(= name "dynamic-wind") (sf-dynamic-wind args env)
|
(= name "dynamic-wind") (sf-dynamic-wind args env)
|
||||||
|
(= name "provide") (sf-provide args env)
|
||||||
|
|
||||||
;; Higher-order forms
|
;; Higher-order forms
|
||||||
(= name "map") (ho-map args env)
|
(= name "map") (ho-map args env)
|
||||||
@@ -949,6 +950,25 @@
|
|||||||
result))))
|
result))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; 6a2. provide — render-time dynamic scope
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;;
|
||||||
|
;; (provide name value body...) — push a named scope with value and empty
|
||||||
|
;; accumulator, evaluate body, pop scope. Returns last body result.
|
||||||
|
|
||||||
|
(define sf-provide
|
||||||
|
(fn ((args :as list) (env :as dict))
|
||||||
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(body-exprs (slice args 2))
|
||||||
|
(result nil))
|
||||||
|
(provide-push! name val)
|
||||||
|
(for-each (fn (e) (set! result (trampoline (eval-expr e env)))) body-exprs)
|
||||||
|
(provide-pop! name)
|
||||||
|
result)))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; 6b. Macro expansion
|
;; 6b. Macro expansion
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -1064,6 +1084,7 @@
|
|||||||
;; (type-of x) → "number" | "string" | "boolean" | "nil"
|
;; (type-of x) → "number" | "string" | "boolean" | "nil"
|
||||||
;; | "symbol" | "keyword" | "list" | "dict"
|
;; | "symbol" | "keyword" | "list" | "dict"
|
||||||
;; | "lambda" | "component" | "macro" | "thunk"
|
;; | "lambda" | "component" | "macro" | "thunk"
|
||||||
|
;; | "spread"
|
||||||
;; (symbol-name sym) → string
|
;; (symbol-name sym) → string
|
||||||
;; (keyword-name kw) → string
|
;; (keyword-name kw) → string
|
||||||
;;
|
;;
|
||||||
@@ -1089,6 +1110,10 @@
|
|||||||
;; (island? x) → boolean
|
;; (island? x) → boolean
|
||||||
;; ;; Islands reuse component accessors: component-params, component-body, etc.
|
;; ;; Islands reuse component accessors: component-params, component-body, etc.
|
||||||
;;
|
;;
|
||||||
|
;; (make-spread attrs) → Spread (attrs dict injected onto parent element)
|
||||||
|
;; (spread? x) → boolean
|
||||||
|
;; (spread-attrs s) → dict
|
||||||
|
;;
|
||||||
;; (macro-params m) → list of strings
|
;; (macro-params m) → list of strings
|
||||||
;; (macro-rest-param m) → string or nil
|
;; (macro-rest-param m) → string or nil
|
||||||
;; (macro-body m) → expr
|
;; (macro-body m) → expr
|
||||||
@@ -1132,4 +1157,9 @@
|
|||||||
;; (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)
|
||||||
;; (call-thunk f env) → value (call a zero-arg function)
|
;; (call-thunk f env) → value (call a zero-arg function)
|
||||||
|
;;
|
||||||
|
;; Render-time accumulators:
|
||||||
|
;; (collect! bucket value) → void (add to named bucket, deduplicated)
|
||||||
|
;; (collected bucket) → list (all values in bucket)
|
||||||
|
;; (clear-collected! bucket) → void (empty the bucket)
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -520,6 +520,18 @@
|
|||||||
"match-route-segments" "matchRouteSegments"
|
"match-route-segments" "matchRouteSegments"
|
||||||
"match-route" "matchRoute"
|
"match-route" "matchRoute"
|
||||||
"find-matching-route" "findMatchingRoute"
|
"find-matching-route" "findMatchingRoute"
|
||||||
|
"make-spread" "makeSpread"
|
||||||
|
"spread?" "isSpread"
|
||||||
|
"spread-attrs" "spreadAttrs"
|
||||||
|
"merge-spread-attrs" "mergeSpreadAttrs"
|
||||||
|
"collect!" "sxCollect"
|
||||||
|
"collected" "sxCollected"
|
||||||
|
"clear-collected!" "sxClearCollected"
|
||||||
|
"provide-push!" "providePush"
|
||||||
|
"provide-pop!" "providePop"
|
||||||
|
"context" "sxContext"
|
||||||
|
"emit!" "sxEmit"
|
||||||
|
"emitted" "sxEmitted"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -864,6 +864,12 @@ PREAMBLE = '''\
|
|||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
|
|
||||||
|
function SxSpread(attrs) { this.attrs = attrs || {}; }
|
||||||
|
SxSpread.prototype._spread = true;
|
||||||
|
|
||||||
|
var _collectBuckets = {};
|
||||||
|
var _provideStacks = {};
|
||||||
|
|
||||||
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; }
|
||||||
|
|
||||||
@@ -1073,6 +1079,22 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
''',
|
''',
|
||||||
|
|
||||||
|
"stdlib.spread": '''
|
||||||
|
// stdlib.spread — spread + collect primitives
|
||||||
|
PRIMITIVES["make-spread"] = makeSpread;
|
||||||
|
PRIMITIVES["spread?"] = isSpread;
|
||||||
|
PRIMITIVES["spread-attrs"] = spreadAttrs;
|
||||||
|
PRIMITIVES["collect!"] = sxCollect;
|
||||||
|
PRIMITIVES["collected"] = sxCollected;
|
||||||
|
PRIMITIVES["clear-collected!"] = sxClearCollected;
|
||||||
|
// provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = providePush;
|
||||||
|
PRIMITIVES["provide-pop!"] = providePop;
|
||||||
|
PRIMITIVES["context"] = sxContext;
|
||||||
|
PRIMITIVES["emit!"] = sxEmit;
|
||||||
|
PRIMITIVES["emitted"] = sxEmitted;
|
||||||
|
''',
|
||||||
}
|
}
|
||||||
# Modules to include by default (all)
|
# Modules to include by default (all)
|
||||||
_ALL_JS_MODULES = list(PRIMITIVES_JS_MODULES.keys())
|
_ALL_JS_MODULES = list(PRIMITIVES_JS_MODULES.keys())
|
||||||
@@ -1108,6 +1130,7 @@ PLATFORM_JS_PRE = '''
|
|||||||
if (x._component) return "component";
|
if (x._component) return "component";
|
||||||
if (x._island) return "island";
|
if (x._island) return "island";
|
||||||
if (x._signal) return "signal";
|
if (x._signal) return "signal";
|
||||||
|
if (x._spread) return "spread";
|
||||||
if (x._macro) return "macro";
|
if (x._macro) return "macro";
|
||||||
if (x._raw) return "raw-html";
|
if (x._raw) return "raw-html";
|
||||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||||
@@ -1130,6 +1153,51 @@ PLATFORM_JS_PRE = '''
|
|||||||
}
|
}
|
||||||
function makeThunk(expr, env) { return new Thunk(expr, env); }
|
function makeThunk(expr, env) { return new Thunk(expr, env); }
|
||||||
|
|
||||||
|
function makeSpread(attrs) { return new SxSpread(attrs || {}); }
|
||||||
|
function isSpread(x) { return x != null && x._spread === true; }
|
||||||
|
function spreadAttrs(s) { return s && s._spread ? s.attrs : {}; }
|
||||||
|
|
||||||
|
function sxCollect(bucket, value) {
|
||||||
|
if (!_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
||||||
|
var items = _collectBuckets[bucket];
|
||||||
|
if (items.indexOf(value) === -1) items.push(value);
|
||||||
|
}
|
||||||
|
function sxCollected(bucket) {
|
||||||
|
return _collectBuckets[bucket] ? _collectBuckets[bucket].slice() : [];
|
||||||
|
}
|
||||||
|
function sxClearCollected(bucket) {
|
||||||
|
if (_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function providePush(name, value) {
|
||||||
|
if (!_provideStacks[name]) _provideStacks[name] = [];
|
||||||
|
_provideStacks[name].push({value: value !== undefined ? value : NIL, emitted: []});
|
||||||
|
}
|
||||||
|
function providePop(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) _provideStacks[name].pop();
|
||||||
|
}
|
||||||
|
function sxContext(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].value;
|
||||||
|
}
|
||||||
|
if (arguments.length > 1) return arguments[1];
|
||||||
|
throw new Error("No provider for: " + name);
|
||||||
|
}
|
||||||
|
function sxEmit(name, value) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
_provideStacks[name][_provideStacks[name].length - 1].emitted.push(value);
|
||||||
|
} else {
|
||||||
|
throw new Error("No provider for emit!: " + name);
|
||||||
|
}
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
function sxEmitted(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].emitted.slice();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
function lambdaParams(f) { return f.params; }
|
function lambdaParams(f) { return f.params; }
|
||||||
function lambdaBody(f) { return f.body; }
|
function lambdaBody(f) { return f.body; }
|
||||||
function lambdaClosure(f) { return f.closure; }
|
function lambdaClosure(f) { return f.closure; }
|
||||||
@@ -3154,6 +3222,17 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has
|
|||||||
api_lines.append(' emitEvent: emitEvent,')
|
api_lines.append(' emitEvent: emitEvent,')
|
||||||
api_lines.append(' onEvent: onEvent,')
|
api_lines.append(' onEvent: onEvent,')
|
||||||
api_lines.append(' bridgeEvent: bridgeEvent,')
|
api_lines.append(' bridgeEvent: bridgeEvent,')
|
||||||
|
api_lines.append(' makeSpread: makeSpread,')
|
||||||
|
api_lines.append(' isSpread: isSpread,')
|
||||||
|
api_lines.append(' spreadAttrs: spreadAttrs,')
|
||||||
|
api_lines.append(' collect: sxCollect,')
|
||||||
|
api_lines.append(' collected: sxCollected,')
|
||||||
|
api_lines.append(' clearCollected: sxClearCollected,')
|
||||||
|
api_lines.append(' providePush: providePush,')
|
||||||
|
api_lines.append(' providePop: providePop,')
|
||||||
|
api_lines.append(' context: sxContext,')
|
||||||
|
api_lines.append(' emit: sxEmit,')
|
||||||
|
api_lines.append(' emitted: sxEmitted,')
|
||||||
api_lines.append(f' _version: "{version}"')
|
api_lines.append(f' _version: "{version}"')
|
||||||
api_lines.append(' };')
|
api_lines.append(' };')
|
||||||
api_lines.append('')
|
api_lines.append('')
|
||||||
|
|||||||
@@ -84,6 +84,63 @@ class _RawHTML:
|
|||||||
self.html = html
|
self.html = html
|
||||||
|
|
||||||
|
|
||||||
|
class _Spread:
|
||||||
|
"""Attribute injection value — merges attrs onto parent element."""
|
||||||
|
__slots__ = ("attrs",)
|
||||||
|
def __init__(self, attrs: dict):
|
||||||
|
self.attrs = dict(attrs) if attrs else {}
|
||||||
|
|
||||||
|
|
||||||
|
# Render-time accumulator buckets (per render pass)
|
||||||
|
_collect_buckets: dict[str, list] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_reset():
|
||||||
|
"""Reset all collect buckets (call at start of each render pass)."""
|
||||||
|
global _collect_buckets
|
||||||
|
_collect_buckets = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Render-time dynamic scope stacks (provide/context/emit!)
|
||||||
|
_provide_stacks: dict[str, list[dict]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def provide_push(name, value=None):
|
||||||
|
"""Push a provider scope with name, value, and empty emitted list."""
|
||||||
|
_provide_stacks.setdefault(name, []).append({"value": value, "emitted": []})
|
||||||
|
|
||||||
|
|
||||||
|
def provide_pop(name):
|
||||||
|
"""Pop the most recent provider scope for name."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name].pop()
|
||||||
|
|
||||||
|
|
||||||
|
def sx_context(name, *default):
|
||||||
|
"""Read value from nearest enclosing provider. Error if no provider and no default."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return _provide_stacks[name][-1]["value"]
|
||||||
|
if default:
|
||||||
|
return default[0]
|
||||||
|
raise RuntimeError(f"No provider for: {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emit(name, value):
|
||||||
|
"""Append value to nearest enclosing provider's accumulator. Error if no provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name][-1]["emitted"].append(value)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"No provider for emit!: {name}")
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emitted(name):
|
||||||
|
"""Return list of values emitted into nearest matching provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return list(_provide_stacks[name][-1]["emitted"])
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def sx_truthy(x):
|
def sx_truthy(x):
|
||||||
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
||||||
if x is False:
|
if x is False:
|
||||||
@@ -167,6 +224,8 @@ def type_of(x):
|
|||||||
return "island"
|
return "island"
|
||||||
if isinstance(x, _Signal):
|
if isinstance(x, _Signal):
|
||||||
return "signal"
|
return "signal"
|
||||||
|
if isinstance(x, _Spread):
|
||||||
|
return "spread"
|
||||||
if isinstance(x, Macro):
|
if isinstance(x, Macro):
|
||||||
return "macro"
|
return "macro"
|
||||||
if isinstance(x, _RawHTML):
|
if isinstance(x, _RawHTML):
|
||||||
@@ -270,6 +329,38 @@ def make_thunk(expr, env):
|
|||||||
return _Thunk(expr, env)
|
return _Thunk(expr, env)
|
||||||
|
|
||||||
|
|
||||||
|
def make_spread(attrs):
|
||||||
|
return _Spread(attrs if isinstance(attrs, dict) else {})
|
||||||
|
|
||||||
|
|
||||||
|
def is_spread(x):
|
||||||
|
return isinstance(x, _Spread)
|
||||||
|
|
||||||
|
|
||||||
|
def spread_attrs(s):
|
||||||
|
return s.attrs if isinstance(s, _Spread) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def sx_collect(bucket, value):
|
||||||
|
"""Add value to named render-time accumulator (deduplicated)."""
|
||||||
|
if bucket not in _collect_buckets:
|
||||||
|
_collect_buckets[bucket] = []
|
||||||
|
items = _collect_buckets[bucket]
|
||||||
|
if value not in items:
|
||||||
|
items.append(value)
|
||||||
|
|
||||||
|
|
||||||
|
def sx_collected(bucket):
|
||||||
|
"""Return all values in named render-time accumulator."""
|
||||||
|
return list(_collect_buckets.get(bucket, []))
|
||||||
|
|
||||||
|
|
||||||
|
def sx_clear_collected(bucket):
|
||||||
|
"""Clear a named render-time accumulator bucket."""
|
||||||
|
if bucket in _collect_buckets:
|
||||||
|
_collect_buckets[bucket] = []
|
||||||
|
|
||||||
|
|
||||||
def lambda_params(f):
|
def lambda_params(f):
|
||||||
return f.params
|
return f.params
|
||||||
|
|
||||||
@@ -881,6 +972,22 @@ def _strip_tags(s):
|
|||||||
"stdlib.debug": '''
|
"stdlib.debug": '''
|
||||||
# stdlib.debug
|
# stdlib.debug
|
||||||
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
||||||
|
''',
|
||||||
|
|
||||||
|
"stdlib.spread": '''
|
||||||
|
# stdlib.spread — spread + collect primitives
|
||||||
|
PRIMITIVES["make-spread"] = make_spread
|
||||||
|
PRIMITIVES["spread?"] = is_spread
|
||||||
|
PRIMITIVES["spread-attrs"] = spread_attrs
|
||||||
|
PRIMITIVES["collect!"] = sx_collect
|
||||||
|
PRIMITIVES["collected"] = sx_collected
|
||||||
|
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
||||||
|
# provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = provide_push
|
||||||
|
PRIMITIVES["provide-pop!"] = provide_pop
|
||||||
|
PRIMITIVES["context"] = sx_context
|
||||||
|
PRIMITIVES["emit!"] = sx_emit
|
||||||
|
PRIMITIVES["emitted"] = sx_emitted
|
||||||
''',
|
''',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,6 +245,18 @@
|
|||||||
"match-route-segments" "match_route_segments"
|
"match-route-segments" "match_route_segments"
|
||||||
"match-route" "match_route"
|
"match-route" "match_route"
|
||||||
"find-matching-route" "find_matching_route"
|
"find-matching-route" "find_matching_route"
|
||||||
|
"make-spread" "make_spread"
|
||||||
|
"spread?" "is_spread"
|
||||||
|
"spread-attrs" "spread_attrs"
|
||||||
|
"merge-spread-attrs" "merge_spread_attrs"
|
||||||
|
"collect!" "sx_collect"
|
||||||
|
"collected" "sx_collected"
|
||||||
|
"clear-collected!" "sx_clear_collected"
|
||||||
|
"provide-push!" "provide_push"
|
||||||
|
"provide-pop!" "provide_pop"
|
||||||
|
"context" "sx_context"
|
||||||
|
"emit!" "sx_emit"
|
||||||
|
"emitted" "sx_emitted"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -213,6 +213,43 @@
|
|||||||
(= (type-of (nth expr 1)) "keyword")))))))))
|
(= (type-of (nth expr 1)) "keyword")))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Spread — attribute injection from children into parent elements
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;;
|
||||||
|
;; A spread value is a dict of attributes that, when returned as a child
|
||||||
|
;; of an HTML element, merges its attrs onto the parent element.
|
||||||
|
;; This enables components to inject classes/styles/data-attrs onto their
|
||||||
|
;; parent without the parent knowing about the specific attrs.
|
||||||
|
;;
|
||||||
|
;; merge-spread-attrs: merge a spread's attrs into an element's attrs dict.
|
||||||
|
;; Class values are joined (space-separated); others overwrite.
|
||||||
|
;; Mutates the target attrs dict in place.
|
||||||
|
|
||||||
|
(define merge-spread-attrs :effects [mutation]
|
||||||
|
(fn ((target :as dict) (spread-dict :as dict))
|
||||||
|
(for-each
|
||||||
|
(fn ((key :as string))
|
||||||
|
(let ((val (dict-get spread-dict key)))
|
||||||
|
(if (= key "class")
|
||||||
|
;; Class: join existing + new with space
|
||||||
|
(let ((existing (dict-get target "class")))
|
||||||
|
(dict-set! target "class"
|
||||||
|
(if (and existing (not (= existing "")))
|
||||||
|
(str existing " " val)
|
||||||
|
val)))
|
||||||
|
;; Style: join with semicolons
|
||||||
|
(if (= key "style")
|
||||||
|
(let ((existing (dict-get target "style")))
|
||||||
|
(dict-set! target "style"
|
||||||
|
(if (and existing (not (= existing "")))
|
||||||
|
(str existing ";" val)
|
||||||
|
val)))
|
||||||
|
;; Everything else: overwrite
|
||||||
|
(dict-set! target key val)))))
|
||||||
|
(keys spread-dict))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; Platform interface (shared across adapters)
|
;; Platform interface (shared across adapters)
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -222,6 +259,23 @@
|
|||||||
;; (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
|
||||||
;;
|
;;
|
||||||
|
;; Spread (render-time attribute injection):
|
||||||
|
;; (make-spread attrs) → Spread value
|
||||||
|
;; (spread? x) → boolean
|
||||||
|
;; (spread-attrs s) → dict
|
||||||
|
;;
|
||||||
|
;; Render-time accumulators:
|
||||||
|
;; (collect! bucket value) → void
|
||||||
|
;; (collected bucket) → list
|
||||||
|
;; (clear-collected! bucket) → void
|
||||||
|
;;
|
||||||
|
;; Dynamic scope (provide/context/emit!):
|
||||||
|
;; (provide-push! name val) → void
|
||||||
|
;; (provide-pop! name) → void
|
||||||
|
;; (context name &rest def) → value from nearest provider
|
||||||
|
;; (emit! name value) → void (append to provider accumulator)
|
||||||
|
;; (emitted name) → list of emitted values
|
||||||
|
;;
|
||||||
;; From parser.sx:
|
;; From parser.sx:
|
||||||
;; (sx-serialize val) → SX source string (aliased as serialize above)
|
;; (sx-serialize val) → SX source string (aliased as serialize above)
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -43,6 +43,63 @@ class _RawHTML:
|
|||||||
self.html = html
|
self.html = html
|
||||||
|
|
||||||
|
|
||||||
|
class _Spread:
|
||||||
|
"""Attribute injection value — merges attrs onto parent element."""
|
||||||
|
__slots__ = ("attrs",)
|
||||||
|
def __init__(self, attrs: dict):
|
||||||
|
self.attrs = dict(attrs) if attrs else {}
|
||||||
|
|
||||||
|
|
||||||
|
# Render-time accumulator buckets (per render pass)
|
||||||
|
_collect_buckets: dict[str, list] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_reset():
|
||||||
|
"""Reset all collect buckets (call at start of each render pass)."""
|
||||||
|
global _collect_buckets
|
||||||
|
_collect_buckets = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Render-time dynamic scope stacks (provide/context/emit!)
|
||||||
|
_provide_stacks: dict[str, list[dict]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def provide_push(name, value=None):
|
||||||
|
"""Push a provider scope with name, value, and empty emitted list."""
|
||||||
|
_provide_stacks.setdefault(name, []).append({"value": value, "emitted": []})
|
||||||
|
|
||||||
|
|
||||||
|
def provide_pop(name):
|
||||||
|
"""Pop the most recent provider scope for name."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name].pop()
|
||||||
|
|
||||||
|
|
||||||
|
def sx_context(name, *default):
|
||||||
|
"""Read value from nearest enclosing provider. Error if no provider and no default."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return _provide_stacks[name][-1]["value"]
|
||||||
|
if default:
|
||||||
|
return default[0]
|
||||||
|
raise RuntimeError(f"No provider for: {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emit(name, value):
|
||||||
|
"""Append value to nearest enclosing provider's accumulator. Error if no provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name][-1]["emitted"].append(value)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"No provider for emit!: {name}")
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emitted(name):
|
||||||
|
"""Return list of values emitted into nearest matching provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return list(_provide_stacks[name][-1]["emitted"])
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def sx_truthy(x):
|
def sx_truthy(x):
|
||||||
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
||||||
if x is False:
|
if x is False:
|
||||||
@@ -126,6 +183,8 @@ def type_of(x):
|
|||||||
return "island"
|
return "island"
|
||||||
if isinstance(x, _Signal):
|
if isinstance(x, _Signal):
|
||||||
return "signal"
|
return "signal"
|
||||||
|
if isinstance(x, _Spread):
|
||||||
|
return "spread"
|
||||||
if isinstance(x, Macro):
|
if isinstance(x, Macro):
|
||||||
return "macro"
|
return "macro"
|
||||||
if isinstance(x, _RawHTML):
|
if isinstance(x, _RawHTML):
|
||||||
@@ -229,6 +288,38 @@ def make_thunk(expr, env):
|
|||||||
return _Thunk(expr, env)
|
return _Thunk(expr, env)
|
||||||
|
|
||||||
|
|
||||||
|
def make_spread(attrs):
|
||||||
|
return _Spread(attrs if isinstance(attrs, dict) else {})
|
||||||
|
|
||||||
|
|
||||||
|
def is_spread(x):
|
||||||
|
return isinstance(x, _Spread)
|
||||||
|
|
||||||
|
|
||||||
|
def spread_attrs(s):
|
||||||
|
return s.attrs if isinstance(s, _Spread) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def sx_collect(bucket, value):
|
||||||
|
"""Add value to named render-time accumulator (deduplicated)."""
|
||||||
|
if bucket not in _collect_buckets:
|
||||||
|
_collect_buckets[bucket] = []
|
||||||
|
items = _collect_buckets[bucket]
|
||||||
|
if value not in items:
|
||||||
|
items.append(value)
|
||||||
|
|
||||||
|
|
||||||
|
def sx_collected(bucket):
|
||||||
|
"""Return all values in named render-time accumulator."""
|
||||||
|
return list(_collect_buckets.get(bucket, []))
|
||||||
|
|
||||||
|
|
||||||
|
def sx_clear_collected(bucket):
|
||||||
|
"""Clear a named render-time accumulator bucket."""
|
||||||
|
if bucket in _collect_buckets:
|
||||||
|
_collect_buckets[bucket] = []
|
||||||
|
|
||||||
|
|
||||||
def lambda_params(f):
|
def lambda_params(f):
|
||||||
return f.params
|
return f.params
|
||||||
|
|
||||||
@@ -847,6 +938,21 @@ def _strip_tags(s):
|
|||||||
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
||||||
|
|
||||||
|
|
||||||
|
# stdlib.spread — spread + collect primitives
|
||||||
|
PRIMITIVES["make-spread"] = make_spread
|
||||||
|
PRIMITIVES["spread?"] = is_spread
|
||||||
|
PRIMITIVES["spread-attrs"] = spread_attrs
|
||||||
|
PRIMITIVES["collect!"] = sx_collect
|
||||||
|
PRIMITIVES["collected"] = sx_collected
|
||||||
|
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
||||||
|
# provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = provide_push
|
||||||
|
PRIMITIVES["provide-pop!"] = provide_pop
|
||||||
|
PRIMITIVES["context"] = sx_context
|
||||||
|
PRIMITIVES["emit!"] = sx_emit
|
||||||
|
PRIMITIVES["emitted"] = sx_emitted
|
||||||
|
|
||||||
|
|
||||||
def is_primitive(name):
|
def is_primitive(name):
|
||||||
if name in PRIMITIVES:
|
if name in PRIMITIVES:
|
||||||
return True
|
return True
|
||||||
@@ -1289,6 +1395,8 @@ def eval_list(expr, env):
|
|||||||
return sf_shift(args, env)
|
return sf_shift(args, env)
|
||||||
elif sx_truthy((name == 'dynamic-wind')):
|
elif sx_truthy((name == 'dynamic-wind')):
|
||||||
return sf_dynamic_wind(args, env)
|
return sf_dynamic_wind(args, env)
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
return sf_provide(args, env)
|
||||||
elif sx_truthy((name == 'map')):
|
elif sx_truthy((name == 'map')):
|
||||||
return ho_map(args, env)
|
return ho_map(args, env)
|
||||||
elif sx_truthy((name == 'map-indexed')):
|
elif sx_truthy((name == 'map-indexed')):
|
||||||
@@ -1780,6 +1888,19 @@ def sf_dynamic_wind(args, env):
|
|||||||
call_thunk(after, env)
|
call_thunk(after, env)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# sf-provide
|
||||||
|
def sf_provide(args, env):
|
||||||
|
_cells = {}
|
||||||
|
name = trampoline(eval_expr(first(args), env))
|
||||||
|
val = trampoline(eval_expr(nth(args, 1), env))
|
||||||
|
body_exprs = slice(args, 2)
|
||||||
|
_cells['result'] = NIL
|
||||||
|
provide_push(name, val)
|
||||||
|
for e in body_exprs:
|
||||||
|
_cells['result'] = trampoline(eval_expr(e, env))
|
||||||
|
provide_pop(name)
|
||||||
|
return _cells['result']
|
||||||
|
|
||||||
# expand-macro
|
# expand-macro
|
||||||
def expand_macro(mac, raw_args, env):
|
def expand_macro(mac, raw_args, env):
|
||||||
local = env_merge(macro_closure(mac), env)
|
local = env_merge(macro_closure(mac), env)
|
||||||
@@ -2056,6 +2177,21 @@ def is_render_expr(expr):
|
|||||||
n = symbol_name(h)
|
n = symbol_name(h)
|
||||||
return ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))))))))
|
return ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))))))))
|
||||||
|
|
||||||
|
# merge-spread-attrs
|
||||||
|
def merge_spread_attrs(target, spread_dict):
|
||||||
|
for key in keys(spread_dict):
|
||||||
|
val = dict_get(spread_dict, key)
|
||||||
|
if sx_truthy((key == 'class')):
|
||||||
|
existing = dict_get(target, 'class')
|
||||||
|
target['class'] = (sx_str(existing, ' ', val) if sx_truthy((existing if not sx_truthy(existing) else (not sx_truthy((existing == ''))))) else val)
|
||||||
|
else:
|
||||||
|
if sx_truthy((key == 'style')):
|
||||||
|
existing = dict_get(target, 'style')
|
||||||
|
target['style'] = (sx_str(existing, ';', val) if sx_truthy((existing if not sx_truthy(existing) else (not sx_truthy((existing == ''))))) else val)
|
||||||
|
else:
|
||||||
|
target[key] = val
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
# === Transpiled from adapter-html ===
|
# === Transpiled from adapter-html ===
|
||||||
|
|
||||||
@@ -2085,6 +2221,8 @@ def render_to_html(expr, env):
|
|||||||
return escape_html(keyword_name(expr))
|
return escape_html(keyword_name(expr))
|
||||||
elif _match == 'raw-html':
|
elif _match == 'raw-html':
|
||||||
return raw_html_content(expr)
|
return raw_html_content(expr)
|
||||||
|
elif _match == 'spread':
|
||||||
|
return expr
|
||||||
else:
|
else:
|
||||||
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
||||||
|
|
||||||
@@ -2106,11 +2244,13 @@ def render_value_to_html(val, env):
|
|||||||
return render_list_to_html(val, env)
|
return render_list_to_html(val, env)
|
||||||
elif _match == 'raw-html':
|
elif _match == 'raw-html':
|
||||||
return raw_html_content(val)
|
return raw_html_content(val)
|
||||||
|
elif _match == 'spread':
|
||||||
|
return val
|
||||||
else:
|
else:
|
||||||
return escape_html(sx_str(val))
|
return escape_html(sx_str(val))
|
||||||
|
|
||||||
# RENDER_HTML_FORMS
|
# RENDER_HTML_FORMS
|
||||||
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each']
|
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'provide']
|
||||||
|
|
||||||
# render-html-form?
|
# render-html-form?
|
||||||
def is_render_html_form(name):
|
def is_render_html_form(name):
|
||||||
@@ -2123,12 +2263,12 @@ def render_list_to_html(expr, env):
|
|||||||
else:
|
else:
|
||||||
head = first(expr)
|
head = first(expr)
|
||||||
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
|
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
|
||||||
return join('', map(lambda x: render_value_to_html(x, env), expr))
|
return join('', filter(lambda x: (not sx_truthy(is_spread(x))), map(lambda x: render_value_to_html(x, env), expr)))
|
||||||
else:
|
else:
|
||||||
name = symbol_name(head)
|
name = symbol_name(head)
|
||||||
args = rest(expr)
|
args = rest(expr)
|
||||||
if sx_truthy((name == '<>')):
|
if sx_truthy((name == '<>')):
|
||||||
return join('', map(lambda x: render_to_html(x, env), args))
|
return join('', filter(lambda x: (not sx_truthy(is_spread(x))), map(lambda x: render_to_html(x, env), args)))
|
||||||
elif sx_truthy((name == 'raw!')):
|
elif sx_truthy((name == 'raw!')):
|
||||||
return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args))
|
return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args))
|
||||||
elif sx_truthy((name == 'lake')):
|
elif sx_truthy((name == 'lake')):
|
||||||
@@ -2169,7 +2309,11 @@ def dispatch_html_form(name, expr, env):
|
|||||||
if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))):
|
if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))):
|
||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
return join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))
|
if sx_truthy((len(expr) == 3)):
|
||||||
|
return render_to_html(nth(expr, 2), env)
|
||||||
|
else:
|
||||||
|
results = map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr)))
|
||||||
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
|
||||||
elif sx_truthy((name == 'cond')):
|
elif sx_truthy((name == 'cond')):
|
||||||
branch = eval_cond(rest(expr), env)
|
branch = eval_cond(rest(expr), env)
|
||||||
if sx_truthy(branch):
|
if sx_truthy(branch):
|
||||||
@@ -2180,26 +2324,43 @@ def dispatch_html_form(name, expr, env):
|
|||||||
return render_to_html(trampoline(eval_expr(expr, env)), env)
|
return render_to_html(trampoline(eval_expr(expr, env)), env)
|
||||||
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
||||||
local = process_bindings(nth(expr, 1), env)
|
local = process_bindings(nth(expr, 1), env)
|
||||||
return join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr))))
|
if sx_truthy((len(expr) == 3)):
|
||||||
|
return render_to_html(nth(expr, 2), local)
|
||||||
|
else:
|
||||||
|
results = map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr)))
|
||||||
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
|
||||||
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
||||||
return join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr))))
|
if sx_truthy((len(expr) == 2)):
|
||||||
|
return render_to_html(nth(expr, 1), env)
|
||||||
|
else:
|
||||||
|
results = map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr)))
|
||||||
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
|
||||||
elif sx_truthy(is_definition_form(name)):
|
elif sx_truthy(is_definition_form(name)):
|
||||||
trampoline(eval_expr(expr, env))
|
trampoline(eval_expr(expr, env))
|
||||||
return ''
|
return ''
|
||||||
elif sx_truthy((name == 'map')):
|
elif sx_truthy((name == 'map')):
|
||||||
f = trampoline(eval_expr(nth(expr, 1), env))
|
f = trampoline(eval_expr(nth(expr, 1), env))
|
||||||
coll = trampoline(eval_expr(nth(expr, 2), env))
|
coll = trampoline(eval_expr(nth(expr, 2), env))
|
||||||
return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))
|
||||||
elif sx_truthy((name == 'map-indexed')):
|
elif sx_truthy((name == 'map-indexed')):
|
||||||
f = trampoline(eval_expr(nth(expr, 1), env))
|
f = trampoline(eval_expr(nth(expr, 1), env))
|
||||||
coll = trampoline(eval_expr(nth(expr, 2), env))
|
coll = trampoline(eval_expr(nth(expr, 2), env))
|
||||||
return join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll)))
|
||||||
elif sx_truthy((name == 'filter')):
|
elif sx_truthy((name == 'filter')):
|
||||||
return render_to_html(trampoline(eval_expr(expr, env)), env)
|
return render_to_html(trampoline(eval_expr(expr, env)), env)
|
||||||
elif sx_truthy((name == 'for-each')):
|
elif sx_truthy((name == 'for-each')):
|
||||||
f = trampoline(eval_expr(nth(expr, 1), env))
|
f = trampoline(eval_expr(nth(expr, 1), env))
|
||||||
coll = trampoline(eval_expr(nth(expr, 2), env))
|
coll = trampoline(eval_expr(nth(expr, 2), env))
|
||||||
return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = trampoline(eval_expr(nth(expr, 1), env))
|
||||||
|
prov_val = trampoline(eval_expr(nth(expr, 2), env))
|
||||||
|
body_start = 3
|
||||||
|
body_count = (len(expr) - 3)
|
||||||
|
provide_push(prov_name, prov_val)
|
||||||
|
result = (render_to_html(nth(expr, body_start), env) if sx_truthy((body_count == 1)) else join('', filter(lambda r: (not sx_truthy(is_spread(r))), map(lambda i: render_to_html(nth(expr, i), env), range(body_start, (body_start + body_count))))))
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
||||||
|
|
||||||
@@ -2218,7 +2379,12 @@ def render_html_component(comp, args, env):
|
|||||||
for p in component_params(comp):
|
for p in component_params(comp):
|
||||||
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
||||||
if sx_truthy(component_has_children(comp)):
|
if sx_truthy(component_has_children(comp)):
|
||||||
local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
|
parts = []
|
||||||
|
for c in children:
|
||||||
|
r = render_to_html(c, env)
|
||||||
|
if sx_truthy((not sx_truthy(is_spread(r)))):
|
||||||
|
parts.append(r)
|
||||||
|
local['children'] = make_raw_html(join('', parts))
|
||||||
return render_to_html(component_body(comp), local)
|
return render_to_html(component_body(comp), local)
|
||||||
|
|
||||||
# render-html-element
|
# render-html-element
|
||||||
@@ -2227,7 +2393,17 @@ def render_html_element(tag, args, env):
|
|||||||
attrs = first(parsed)
|
attrs = first(parsed)
|
||||||
children = nth(parsed, 1)
|
children = nth(parsed, 1)
|
||||||
is_void = contains_p(VOID_ELEMENTS, tag)
|
is_void = contains_p(VOID_ELEMENTS, tag)
|
||||||
return sx_str('<', tag, render_attrs(attrs), (' />' if sx_truthy(is_void) else sx_str('>', join('', map(lambda c: render_to_html(c, env), children)), '</', tag, '>')))
|
if sx_truthy(is_void):
|
||||||
|
return sx_str('<', tag, render_attrs(attrs), ' />')
|
||||||
|
else:
|
||||||
|
content_parts = []
|
||||||
|
for c in children:
|
||||||
|
result = render_to_html(c, env)
|
||||||
|
if sx_truthy(is_spread(result)):
|
||||||
|
merge_spread_attrs(attrs, spread_attrs(result))
|
||||||
|
else:
|
||||||
|
content_parts.append(result)
|
||||||
|
return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '</', tag, '>')
|
||||||
|
|
||||||
# render-html-lake
|
# render-html-lake
|
||||||
def render_html_lake(args, env):
|
def render_html_lake(args, env):
|
||||||
@@ -2236,7 +2412,15 @@ def render_html_lake(args, env):
|
|||||||
_cells['lake_tag'] = 'div'
|
_cells['lake_tag'] = 'div'
|
||||||
children = []
|
children = []
|
||||||
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) 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)
|
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) 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)
|
||||||
return sx_str('<', _cells['lake_tag'], ' data-sx-lake="', escape_attr((_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')), '">', join('', map(lambda c: render_to_html(c, env), children)), '</', _cells['lake_tag'], '>')
|
lake_attrs = {'data-sx-lake': (_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')}
|
||||||
|
content_parts = []
|
||||||
|
for c in children:
|
||||||
|
result = render_to_html(c, env)
|
||||||
|
if sx_truthy(is_spread(result)):
|
||||||
|
merge_spread_attrs(lake_attrs, spread_attrs(result))
|
||||||
|
else:
|
||||||
|
content_parts.append(result)
|
||||||
|
return sx_str('<', _cells['lake_tag'], render_attrs(lake_attrs), '>', join('', content_parts), '</', _cells['lake_tag'], '>')
|
||||||
|
|
||||||
# render-html-marsh
|
# render-html-marsh
|
||||||
def render_html_marsh(args, env):
|
def render_html_marsh(args, env):
|
||||||
@@ -2245,7 +2429,15 @@ def render_html_marsh(args, env):
|
|||||||
_cells['marsh_tag'] = 'div'
|
_cells['marsh_tag'] = 'div'
|
||||||
children = []
|
children = []
|
||||||
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) 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)
|
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) 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)
|
||||||
return sx_str('<', _cells['marsh_tag'], ' data-sx-marsh="', escape_attr((_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')), '">', join('', map(lambda c: render_to_html(c, env), children)), '</', _cells['marsh_tag'], '>')
|
marsh_attrs = {'data-sx-marsh': (_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')}
|
||||||
|
content_parts = []
|
||||||
|
for c in children:
|
||||||
|
result = render_to_html(c, env)
|
||||||
|
if sx_truthy(is_spread(result)):
|
||||||
|
merge_spread_attrs(marsh_attrs, spread_attrs(result))
|
||||||
|
else:
|
||||||
|
content_parts.append(result)
|
||||||
|
return sx_str('<', _cells['marsh_tag'], render_attrs(marsh_attrs), '>', join('', content_parts), '</', _cells['marsh_tag'], '>')
|
||||||
|
|
||||||
# render-html-island
|
# render-html-island
|
||||||
def render_html_island(island, args, env):
|
def render_html_island(island, args, env):
|
||||||
@@ -2257,7 +2449,12 @@ def render_html_island(island, args, env):
|
|||||||
for p in component_params(island):
|
for p in component_params(island):
|
||||||
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
||||||
if sx_truthy(component_has_children(island)):
|
if sx_truthy(component_has_children(island)):
|
||||||
local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
|
parts = []
|
||||||
|
for c in children:
|
||||||
|
r = render_to_html(c, env)
|
||||||
|
if sx_truthy((not sx_truthy(is_spread(r)))):
|
||||||
|
parts.append(r)
|
||||||
|
local['children'] = make_raw_html(join('', parts))
|
||||||
body_html = render_to_html(component_body(island), local)
|
body_html = render_to_html(component_body(island), local)
|
||||||
state_sx = serialize_island_state(kwargs)
|
state_sx = serialize_island_state(kwargs)
|
||||||
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_sx), '"') if sx_truthy(state_sx) else ''), '>', body_html, '</span>')
|
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_sx), '"') if sx_truthy(state_sx) else ''), '>', body_html, '</span>')
|
||||||
@@ -2313,6 +2510,8 @@ def aser(expr, env):
|
|||||||
return []
|
return []
|
||||||
else:
|
else:
|
||||||
return aser_list(expr, env)
|
return aser_list(expr, env)
|
||||||
|
elif _match == 'spread':
|
||||||
|
return expr
|
||||||
else:
|
else:
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
@@ -2400,7 +2599,7 @@ def aser_call(name, args, env):
|
|||||||
return sx_str('(', join(' ', parts), ')')
|
return sx_str('(', join(' ', parts), ')')
|
||||||
|
|
||||||
# SPECIAL_FORM_NAMES
|
# SPECIAL_FORM_NAMES
|
||||||
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect']
|
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect', 'provide']
|
||||||
|
|
||||||
# HO_FORM_NAMES
|
# HO_FORM_NAMES
|
||||||
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
||||||
@@ -2497,6 +2696,15 @@ def aser_special(name, expr, env):
|
|||||||
elif sx_truthy(((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') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
|
elif sx_truthy(((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') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
|
||||||
trampoline(eval_expr(expr, env))
|
trampoline(eval_expr(expr, env))
|
||||||
return NIL
|
return NIL
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = trampoline(eval_expr(first(args), env))
|
||||||
|
prov_val = trampoline(eval_expr(nth(args, 1), env))
|
||||||
|
_cells['result'] = NIL
|
||||||
|
provide_push(prov_name, prov_val)
|
||||||
|
for body in slice(args, 2):
|
||||||
|
_cells['result'] = aser(body, env)
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return _cells['result']
|
||||||
else:
|
else:
|
||||||
return trampoline(eval_expr(expr, env))
|
return trampoline(eval_expr(expr, env))
|
||||||
|
|
||||||
@@ -3441,6 +3649,8 @@ async def async_render(expr, env, ctx):
|
|||||||
return escape_html(sx_str(expr))
|
return escape_html(sx_str(expr))
|
||||||
elif _match == 'raw-html':
|
elif _match == 'raw-html':
|
||||||
return raw_html_content(expr)
|
return raw_html_content(expr)
|
||||||
|
elif _match == 'spread':
|
||||||
|
return expr
|
||||||
elif _match == 'symbol':
|
elif _match == 'symbol':
|
||||||
val = (await async_eval(expr, env, ctx))
|
val = (await async_eval(expr, env, ctx))
|
||||||
return (await async_render(val, env, ctx))
|
return (await async_render(val, env, ctx))
|
||||||
@@ -3472,7 +3682,7 @@ async def async_render_list(expr, env, ctx):
|
|||||||
elif sx_truthy((name == 'raw!')):
|
elif sx_truthy((name == 'raw!')):
|
||||||
return (await async_render_raw(args, env, ctx))
|
return (await async_render_raw(args, env, ctx))
|
||||||
elif sx_truthy((name == '<>')):
|
elif sx_truthy((name == '<>')):
|
||||||
return join('', (await async_map_render(args, env, ctx)))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_render(args, env, ctx))))
|
||||||
elif sx_truthy(starts_with_p(name, 'html:')):
|
elif sx_truthy(starts_with_p(name, 'html:')):
|
||||||
return (await async_render_element(slice(name, 5), args, env, ctx))
|
return (await async_render_element(slice(name, 5), args, env, ctx))
|
||||||
elif sx_truthy(async_render_form_p(name)):
|
elif sx_truthy(async_render_form_p(name)):
|
||||||
@@ -3522,15 +3732,20 @@ async def async_render_element(tag, args, env, ctx):
|
|||||||
class_val = dict_get(attrs, 'class')
|
class_val = dict_get(attrs, 'class')
|
||||||
if sx_truthy(((not sx_truthy(is_nil(class_val))) if not sx_truthy((not sx_truthy(is_nil(class_val)))) else (not sx_truthy((class_val == False))))):
|
if sx_truthy(((not sx_truthy(is_nil(class_val))) if not sx_truthy((not sx_truthy(is_nil(class_val)))) else (not sx_truthy((class_val == False))))):
|
||||||
css_class_collect(sx_str(class_val))
|
css_class_collect(sx_str(class_val))
|
||||||
opening = sx_str('<', tag, render_attrs(attrs), '>')
|
|
||||||
if sx_truthy(contains_p(VOID_ELEMENTS, tag)):
|
if sx_truthy(contains_p(VOID_ELEMENTS, tag)):
|
||||||
return opening
|
return sx_str('<', tag, render_attrs(attrs), '>')
|
||||||
else:
|
else:
|
||||||
token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL)
|
token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL)
|
||||||
child_html = join('', (await async_map_render(children, env, ctx)))
|
content_parts = []
|
||||||
|
for c in children:
|
||||||
|
result = (await async_render(c, env, ctx))
|
||||||
|
if sx_truthy(is_spread(result)):
|
||||||
|
merge_spread_attrs(attrs, spread_attrs(result))
|
||||||
|
else:
|
||||||
|
content_parts.append(result)
|
||||||
if sx_truthy(token):
|
if sx_truthy(token):
|
||||||
svg_context_reset(token)
|
svg_context_reset(token)
|
||||||
return sx_str(opening, child_html, '</', tag, '>')
|
return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '</', tag, '>')
|
||||||
|
|
||||||
# async-parse-element-args
|
# async-parse-element-args
|
||||||
async def async_parse_element_args(args, attrs, children, env, ctx):
|
async def async_parse_element_args(args, attrs, children, env, ctx):
|
||||||
@@ -3561,7 +3776,12 @@ async def async_render_component(comp, args, env, ctx):
|
|||||||
for p in component_params(comp):
|
for p in component_params(comp):
|
||||||
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
||||||
if sx_truthy(component_has_children(comp)):
|
if sx_truthy(component_has_children(comp)):
|
||||||
local['children'] = make_raw_html(join('', (await async_map_render(children, env, ctx))))
|
parts = []
|
||||||
|
for c in children:
|
||||||
|
r = (await async_render(c, env, ctx))
|
||||||
|
if sx_truthy((not sx_truthy(is_spread(r)))):
|
||||||
|
parts.append(r)
|
||||||
|
local['children'] = make_raw_html(join('', parts))
|
||||||
return (await async_render(component_body(comp), local, ctx))
|
return (await async_render(component_body(comp), local, ctx))
|
||||||
|
|
||||||
# async-render-island
|
# async-render-island
|
||||||
@@ -3574,7 +3794,12 @@ async def async_render_island(island, args, env, ctx):
|
|||||||
for p in component_params(island):
|
for p in component_params(island):
|
||||||
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
||||||
if sx_truthy(component_has_children(island)):
|
if sx_truthy(component_has_children(island)):
|
||||||
local['children'] = make_raw_html(join('', (await async_map_render(children, env, ctx))))
|
parts = []
|
||||||
|
for c in children:
|
||||||
|
r = (await async_render(c, env, ctx))
|
||||||
|
if sx_truthy((not sx_truthy(is_spread(r)))):
|
||||||
|
parts.append(r)
|
||||||
|
local['children'] = make_raw_html(join('', parts))
|
||||||
body_html = (await async_render(component_body(island), local, ctx))
|
body_html = (await async_render(component_body(island), local, ctx))
|
||||||
state_json = serialize_island_state(kwargs)
|
state_json = serialize_island_state(kwargs)
|
||||||
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_json), '"') if sx_truthy(state_json) else ''), '>', body_html, '</span>')
|
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_json), '"') if sx_truthy(state_json) else ''), '>', body_html, '</span>')
|
||||||
@@ -3613,7 +3838,7 @@ async def async_map_render(exprs, env, ctx):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
# ASYNC_RENDER_FORMS
|
# ASYNC_RENDER_FORMS
|
||||||
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each']
|
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'provide']
|
||||||
|
|
||||||
# async-render-form?
|
# async-render-form?
|
||||||
def async_render_form_p(name):
|
def async_render_form_p(name):
|
||||||
@@ -3634,7 +3859,11 @@ async def dispatch_async_render_form(name, expr, env, ctx):
|
|||||||
if sx_truthy((not sx_truthy((await async_eval(nth(expr, 1), env, ctx))))):
|
if sx_truthy((not sx_truthy((await async_eval(nth(expr, 1), env, ctx))))):
|
||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
return join('', (await async_map_render(slice(expr, 2), env, ctx)))
|
if sx_truthy((len(expr) == 3)):
|
||||||
|
return (await async_render(nth(expr, 2), env, ctx))
|
||||||
|
else:
|
||||||
|
results = (await async_map_render(slice(expr, 2), env, ctx))
|
||||||
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
|
||||||
elif sx_truthy((name == 'cond')):
|
elif sx_truthy((name == 'cond')):
|
||||||
clauses = rest(expr)
|
clauses = rest(expr)
|
||||||
if sx_truthy(cond_scheme_p(clauses)):
|
if sx_truthy(cond_scheme_p(clauses)):
|
||||||
@@ -3645,26 +3874,43 @@ async def dispatch_async_render_form(name, expr, env, ctx):
|
|||||||
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
||||||
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
||||||
local = (await async_process_bindings(nth(expr, 1), env, ctx))
|
local = (await async_process_bindings(nth(expr, 1), env, ctx))
|
||||||
return join('', (await async_map_render(slice(expr, 2), local, ctx)))
|
if sx_truthy((len(expr) == 3)):
|
||||||
|
return (await async_render(nth(expr, 2), local, ctx))
|
||||||
|
else:
|
||||||
|
results = (await async_map_render(slice(expr, 2), local, ctx))
|
||||||
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
|
||||||
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
||||||
return join('', (await async_map_render(rest(expr), env, ctx)))
|
if sx_truthy((len(expr) == 2)):
|
||||||
|
return (await async_render(nth(expr, 1), env, ctx))
|
||||||
|
else:
|
||||||
|
results = (await async_map_render(rest(expr), env, ctx))
|
||||||
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
|
||||||
elif sx_truthy(is_definition_form(name)):
|
elif sx_truthy(is_definition_form(name)):
|
||||||
(await async_eval(expr, env, ctx))
|
(await async_eval(expr, env, ctx))
|
||||||
return ''
|
return ''
|
||||||
elif sx_truthy((name == 'map')):
|
elif sx_truthy((name == 'map')):
|
||||||
f = (await async_eval(nth(expr, 1), env, ctx))
|
f = (await async_eval(nth(expr, 1), env, ctx))
|
||||||
coll = (await async_eval(nth(expr, 2), env, ctx))
|
coll = (await async_eval(nth(expr, 2), env, ctx))
|
||||||
return join('', (await async_map_fn_render(f, coll, env, ctx)))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_fn_render(f, coll, env, ctx))))
|
||||||
elif sx_truthy((name == 'map-indexed')):
|
elif sx_truthy((name == 'map-indexed')):
|
||||||
f = (await async_eval(nth(expr, 1), env, ctx))
|
f = (await async_eval(nth(expr, 1), env, ctx))
|
||||||
coll = (await async_eval(nth(expr, 2), env, ctx))
|
coll = (await async_eval(nth(expr, 2), env, ctx))
|
||||||
return join('', (await async_map_indexed_fn_render(f, coll, env, ctx)))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_indexed_fn_render(f, coll, env, ctx))))
|
||||||
elif sx_truthy((name == 'filter')):
|
elif sx_truthy((name == 'filter')):
|
||||||
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
||||||
elif sx_truthy((name == 'for-each')):
|
elif sx_truthy((name == 'for-each')):
|
||||||
f = (await async_eval(nth(expr, 1), env, ctx))
|
f = (await async_eval(nth(expr, 1), env, ctx))
|
||||||
coll = (await async_eval(nth(expr, 2), env, ctx))
|
coll = (await async_eval(nth(expr, 2), env, ctx))
|
||||||
return join('', (await async_map_fn_render(f, coll, env, ctx)))
|
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_fn_render(f, coll, env, ctx))))
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = (await async_eval(nth(expr, 1), env, ctx))
|
||||||
|
prov_val = (await async_eval(nth(expr, 2), env, ctx))
|
||||||
|
body_start = 3
|
||||||
|
body_count = (len(expr) - 3)
|
||||||
|
provide_push(prov_name, prov_val)
|
||||||
|
result = ((await async_render(nth(expr, body_start), env, ctx)) if sx_truthy((body_count == 1)) else (lambda results: join('', filter(lambda r: (not sx_truthy(is_spread(r))), results)))((await async_map_render(slice(expr, body_start), env, ctx))))
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
||||||
|
|
||||||
@@ -3791,6 +4037,8 @@ async def async_aser(expr, env, ctx):
|
|||||||
return keyword_name(expr)
|
return keyword_name(expr)
|
||||||
elif _match == 'dict':
|
elif _match == 'dict':
|
||||||
return (await async_aser_dict(expr, env, ctx))
|
return (await async_aser_dict(expr, env, ctx))
|
||||||
|
elif _match == 'spread':
|
||||||
|
return expr
|
||||||
elif _match == 'list':
|
elif _match == 'list':
|
||||||
if sx_truthy(empty_p(expr)):
|
if sx_truthy(empty_p(expr)):
|
||||||
return []
|
return []
|
||||||
@@ -3988,7 +4236,7 @@ async def async_aser_call(name, args, env, ctx):
|
|||||||
return make_sx_expr(sx_str('(', join(' ', parts), ')'))
|
return make_sx_expr(sx_str('(', join(' ', parts), ')'))
|
||||||
|
|
||||||
# ASYNC_ASER_FORM_NAMES
|
# ASYNC_ASER_FORM_NAMES
|
||||||
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect']
|
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect', 'provide']
|
||||||
|
|
||||||
# ASYNC_ASER_HO_NAMES
|
# ASYNC_ASER_HO_NAMES
|
||||||
ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
|
ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
|
||||||
@@ -4082,6 +4330,15 @@ async def dispatch_async_aser_form(name, expr, env, ctx):
|
|||||||
elif sx_truthy(((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') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
|
elif sx_truthy(((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') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
|
||||||
(await async_eval(expr, env, ctx))
|
(await async_eval(expr, env, ctx))
|
||||||
return NIL
|
return NIL
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = (await async_eval(first(args), env, ctx))
|
||||||
|
prov_val = (await async_eval(nth(args, 1), env, ctx))
|
||||||
|
_cells['result'] = NIL
|
||||||
|
provide_push(prov_name, prov_val)
|
||||||
|
for body in slice(args, 2):
|
||||||
|
_cells['result'] = (await async_aser(body, env, ctx))
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return _cells['result']
|
||||||
else:
|
else:
|
||||||
return (await async_eval(expr, env, ctx))
|
return (await async_eval(expr, env, ctx))
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +1,30 @@
|
|||||||
;; @client — send all define forms to browser for client-side use.
|
;; @client — send all define forms to browser for client-side use.
|
||||||
;; CSSX — computed CSS from s-expressions.
|
;; CSSX — computed CSS from s-expressions.
|
||||||
;;
|
;;
|
||||||
;; Generic mechanism: cssx is a macro that groups CSS property declarations.
|
;; Tailwind-style utility component using spread + collect primitives.
|
||||||
;; The vocabulary (property mappings, value functions) is pluggable — the
|
;; Use as a child of any element — injects classes onto the parent:
|
||||||
;; Tailwind-inspired defaults below are just one possible style system.
|
|
||||||
;;
|
;;
|
||||||
;; Usage:
|
;; (div (~cssx/tw "bg-yellow-199 text-violet-700 p-4 font-bold")
|
||||||
;; (cssx (:text (colour "violet" 699) (size "4xl") (weight "bold") (family "mono"))
|
;; "content")
|
||||||
;; (:bg (colour "stone" 50)))
|
|
||||||
;;
|
;;
|
||||||
;; Each group is (:keyword value ...modifiers):
|
;; (button (~cssx/tw "hover:bg-rose-500 md:text-xl")
|
||||||
;; - keyword maps to a CSS property via cssx-properties dict
|
;; "click me")
|
||||||
;; - value is the CSS value for that property
|
|
||||||
;; - modifiers are extra CSS declaration strings, concatenated in
|
|
||||||
;;
|
;;
|
||||||
;; Single group:
|
;; Each token becomes a deterministic class + JIT CSS rule.
|
||||||
;; (cssx (:text (colour "violet" 699)))
|
;; Rules are collected into the "cssx" bucket, flushed once by ~cssx/flush.
|
||||||
|
;; No wrapper elements, no per-element <style> tags.
|
||||||
;;
|
;;
|
||||||
;; Modifiers without a colour:
|
;; Reusable style variables:
|
||||||
;; (cssx (:text nil (size "4xl") (weight "bold")))
|
;; (define fancy (~cssx/tw "font-bold text-violet-700 text-4xl"))
|
||||||
|
;; (div fancy "styled content")
|
||||||
;;
|
;;
|
||||||
;; Unknown keywords pass through as raw CSS property names:
|
;; This is one instance of the CSSX component pattern — other styling
|
||||||
;; (cssx (:outline (colour "red" 500))) → "outline:hsl(0,72%,53%);"
|
;; components are possible with different vocabulary.
|
||||||
;;
|
|
||||||
;; Standalone modifiers work outside cssx too:
|
|
||||||
;; :style (size "4xl")
|
|
||||||
;; :style (str (weight "bold") (family "mono"))
|
|
||||||
|
|
||||||
;; =========================================================================
|
;; =========================================================================
|
||||||
;; Layer 1: Generic mechanism — cssx macro + cssxgroup function
|
;; Colour data — hue/saturation bases + shade-to-lightness curve
|
||||||
;; =========================================================================
|
;; =========================================================================
|
||||||
|
|
||||||
;; Property keyword → CSS property name. Extend this dict for new mappings.
|
|
||||||
(define cssx-properties
|
|
||||||
{"text" "color"
|
|
||||||
"bg" "background-color"
|
|
||||||
"border" "border-color"})
|
|
||||||
|
|
||||||
;; Evaluate one property group: (:text value modifier1 modifier2 ...)
|
|
||||||
;; If value is nil, only modifiers are emitted (no property declaration).
|
|
||||||
;; NOTE: name must NOT contain hyphens — the evaluator's isRenderExpr check
|
|
||||||
;; treats (hyphenated-name :keyword ...) as a custom HTML element.
|
|
||||||
(define cssxgroup
|
|
||||||
(fn (prop value b c d e)
|
|
||||||
(let ((css-prop (or (get cssx-properties prop) prop)))
|
|
||||||
(str (if (nil? value) "" (str css-prop ":" value ";"))
|
|
||||||
(or b "") (or c "") (or d "") (or e "")))))
|
|
||||||
|
|
||||||
;; cssx macro — takes one or more property groups, expands to (str ...).
|
|
||||||
;; (cssx (:text val ...) (:bg val ...))
|
|
||||||
;; → (str (cssxgroup :text val ...) (cssxgroup :bg val ...))
|
|
||||||
(defmacro cssx (&rest groups)
|
|
||||||
`(str ,@(map (fn (g) (cons 'cssxgroup g)) groups)))
|
|
||||||
|
|
||||||
;; =========================================================================
|
|
||||||
;; Layer 2: Value vocabulary — colour, size, weight, family
|
|
||||||
;; These are independent functions. Use inside cssx groups or standalone.
|
|
||||||
;; Replace or extend with any style system.
|
|
||||||
;; =========================================================================
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; Colour — compute CSS colour value from name + shade
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define colour-bases
|
(define colour-bases
|
||||||
{"violet" {"h" 263 "s" 70}
|
{"violet" {"h" 263 "s" 70}
|
||||||
"purple" {"h" 271 "s" 81}
|
"purple" {"h" 271 "s" 81}
|
||||||
@@ -84,7 +46,9 @@
|
|||||||
"slate" {"h" 215 "s" 16}
|
"slate" {"h" 215 "s" 16}
|
||||||
"gray" {"h" 220 "s" 9}
|
"gray" {"h" 220 "s" 9}
|
||||||
"zinc" {"h" 240 "s" 5}
|
"zinc" {"h" 240 "s" 5}
|
||||||
"neutral" {"h" 0 "s" 0}})
|
"neutral" {"h" 0 "s" 0}
|
||||||
|
"white" {"h" 0 "s" 0}
|
||||||
|
"black" {"h" 0 "s" 0}})
|
||||||
|
|
||||||
(define lerp (fn (a b t) (+ a (* t (- b a)))))
|
(define lerp (fn (a b t) (+ a (* t (- b a)))))
|
||||||
|
|
||||||
@@ -114,29 +78,50 @@
|
|||||||
(l (shade-to-lightness shade)))
|
(l (shade-to-lightness shade)))
|
||||||
(str "hsl(" h "," s "%," (round l) "%)"))))))
|
(str "hsl(" h "," s "%," (round l) "%)"))))))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; =========================================================================
|
||||||
;; Font sizes — named size → font-size + line-height (Tailwind v3 scale)
|
;; Lookup tables — all the value vocabularies
|
||||||
;; ---------------------------------------------------------------------------
|
;; =========================================================================
|
||||||
|
|
||||||
|
;; Colour property shorthands → CSS property name
|
||||||
|
(define cssx-colour-props
|
||||||
|
{"bg" "background-color"
|
||||||
|
"text" "color"
|
||||||
|
"border" "border-color"})
|
||||||
|
|
||||||
|
;; Spacing property shorthands → CSS declaration template ({v} = computed value)
|
||||||
|
(define cssx-spacing-props
|
||||||
|
{"p" "padding:{v}"
|
||||||
|
"px" "padding-left:{v};padding-right:{v}"
|
||||||
|
"py" "padding-top:{v};padding-bottom:{v}"
|
||||||
|
"pt" "padding-top:{v}"
|
||||||
|
"pb" "padding-bottom:{v}"
|
||||||
|
"pl" "padding-left:{v}"
|
||||||
|
"pr" "padding-right:{v}"
|
||||||
|
"m" "margin:{v}"
|
||||||
|
"mx" "margin-left:{v};margin-right:{v}"
|
||||||
|
"my" "margin-top:{v};margin-bottom:{v}"
|
||||||
|
"mt" "margin-top:{v}"
|
||||||
|
"mb" "margin-bottom:{v}"
|
||||||
|
"ml" "margin-left:{v}"
|
||||||
|
"mr" "margin-right:{v}"})
|
||||||
|
|
||||||
|
;; Named font sizes (Tailwind v3 scale)
|
||||||
(define cssx-sizes
|
(define cssx-sizes
|
||||||
{"xs" "font-size:0.75rem;line-height:1rem;"
|
{"xs" "font-size:0.75rem;line-height:1rem"
|
||||||
"sm" "font-size:0.875rem;line-height:1.25rem;"
|
"sm" "font-size:0.875rem;line-height:1.25rem"
|
||||||
"base" "font-size:1rem;line-height:1.5rem;"
|
"base" "font-size:1rem;line-height:1.5rem"
|
||||||
"lg" "font-size:1.125rem;line-height:1.75rem;"
|
"lg" "font-size:1.125rem;line-height:1.75rem"
|
||||||
"xl" "font-size:1.25rem;line-height:1.75rem;"
|
"xl" "font-size:1.25rem;line-height:1.75rem"
|
||||||
"2xl" "font-size:1.5rem;line-height:2rem;"
|
"2xl" "font-size:1.5rem;line-height:2rem"
|
||||||
"3xl" "font-size:1.875rem;line-height:2.25rem;"
|
"3xl" "font-size:1.875rem;line-height:2.25rem"
|
||||||
"4xl" "font-size:2.25rem;line-height:2.5rem;"
|
"4xl" "font-size:2.25rem;line-height:2.5rem"
|
||||||
"5xl" "font-size:3rem;line-height:1;"
|
"5xl" "font-size:3rem;line-height:1"
|
||||||
"6xl" "font-size:3.75rem;line-height:1;"
|
"6xl" "font-size:3.75rem;line-height:1"
|
||||||
"7xl" "font-size:4.5rem;line-height:1;"
|
"7xl" "font-size:4.5rem;line-height:1"
|
||||||
"8xl" "font-size:6rem;line-height:1;"
|
"8xl" "font-size:6rem;line-height:1"
|
||||||
"9xl" "font-size:8rem;line-height:1;"})
|
"9xl" "font-size:8rem;line-height:1"})
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
;; Font weights — named weight → numeric value
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
;; Named font weights
|
||||||
(define cssx-weights
|
(define cssx-weights
|
||||||
{"thin" "100"
|
{"thin" "100"
|
||||||
"extralight" "200"
|
"extralight" "200"
|
||||||
@@ -148,67 +133,25 @@
|
|||||||
"extrabold" "800"
|
"extrabold" "800"
|
||||||
"black" "900"})
|
"black" "900"})
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; Named font families
|
||||||
;; Font families — named family → CSS font stack
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(define cssx-families
|
(define cssx-families
|
||||||
{"sans" "ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif"
|
{"sans" "ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif"
|
||||||
"serif" "ui-serif,Georgia,Cambria,\"Times New Roman\",Times,serif"
|
"serif" "ui-serif,Georgia,Cambria,\"Times New Roman\",Times,serif"
|
||||||
"mono" "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace"})
|
"mono" "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace"})
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; Text alignment keywords
|
||||||
;; Standalone modifier functions — return CSS declaration strings
|
(define cssx-alignments
|
||||||
;; Each returns a complete CSS declaration string. Use inside cssx groups
|
{"left" true "center" true "right" true "justify" true})
|
||||||
;; or standalone on :style with str.
|
|
||||||
;; ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
;; -- Typography --
|
;; Display keywords → CSS value
|
||||||
|
(define cssx-displays
|
||||||
(define size
|
{"block" "block"
|
||||||
(fn (s) (or (get cssx-sizes s) (str "font-size:" s ";"))))
|
"inline" "inline"
|
||||||
|
"inline-block" "inline-block"
|
||||||
(define weight
|
"flex" "flex"
|
||||||
(fn (w)
|
"inline-flex" "inline-flex"
|
||||||
(let ((v (get cssx-weights w)))
|
"grid" "grid"
|
||||||
(str "font-weight:" (or v w) ";"))))
|
"hidden" "none"})
|
||||||
|
|
||||||
(define family
|
|
||||||
(fn (f)
|
|
||||||
(let ((v (get cssx-families f)))
|
|
||||||
(str "font-family:" (or v f) ";"))))
|
|
||||||
|
|
||||||
(define align
|
|
||||||
(fn (a) (str "text-align:" a ";")))
|
|
||||||
|
|
||||||
(define decoration
|
|
||||||
(fn (d) (str "text-decoration:" d ";")))
|
|
||||||
|
|
||||||
;; -- Spacing (Tailwind scale: 1 unit = 0.25rem) --
|
|
||||||
|
|
||||||
(define spacing (fn (n) (str (* n 0.25) "rem")))
|
|
||||||
|
|
||||||
(define p (fn (n) (str "padding:" (spacing n) ";")))
|
|
||||||
(define px (fn (n) (str "padding-left:" (spacing n) ";padding-right:" (spacing n) ";")))
|
|
||||||
(define py (fn (n) (str "padding-top:" (spacing n) ";padding-bottom:" (spacing n) ";")))
|
|
||||||
(define pt (fn (n) (str "padding-top:" (spacing n) ";")))
|
|
||||||
(define pb (fn (n) (str "padding-bottom:" (spacing n) ";")))
|
|
||||||
(define pl (fn (n) (str "padding-left:" (spacing n) ";")))
|
|
||||||
(define pr (fn (n) (str "padding-right:" (spacing n) ";")))
|
|
||||||
|
|
||||||
(define m (fn (n) (str "margin:" (spacing n) ";")))
|
|
||||||
(define mx (fn (n) (str "margin-left:" (spacing n) ";margin-right:" (spacing n) ";")))
|
|
||||||
(define my (fn (n) (str "margin-top:" (spacing n) ";margin-bottom:" (spacing n) ";")))
|
|
||||||
(define mt (fn (n) (str "margin-top:" (spacing n) ";")))
|
|
||||||
(define mb (fn (n) (str "margin-bottom:" (spacing n) ";")))
|
|
||||||
(define ml (fn (n) (str "margin-left:" (spacing n) ";")))
|
|
||||||
(define mr (fn (n) (str "margin-right:" (spacing n) ";")))
|
|
||||||
(define mx-auto (fn () "margin-left:auto;margin-right:auto;"))
|
|
||||||
|
|
||||||
;; -- Display & layout --
|
|
||||||
|
|
||||||
(define display (fn (d) (str "display:" d ";")))
|
|
||||||
(define max-w (fn (w) (str "max-width:" w ";")))
|
|
||||||
|
|
||||||
;; Named max-widths (Tailwind scale)
|
;; Named max-widths (Tailwind scale)
|
||||||
(define cssx-max-widths
|
(define cssx-max-widths
|
||||||
@@ -216,4 +159,342 @@
|
|||||||
"lg" "32rem" "xl" "36rem" "2xl" "42rem"
|
"lg" "32rem" "xl" "36rem" "2xl" "42rem"
|
||||||
"3xl" "48rem" "4xl" "56rem" "5xl" "64rem"
|
"3xl" "48rem" "4xl" "56rem" "5xl" "64rem"
|
||||||
"6xl" "72rem" "7xl" "80rem"
|
"6xl" "72rem" "7xl" "80rem"
|
||||||
"full" "100%" "none" "none"})
|
"full" "100%" "none" "none"
|
||||||
|
"prose" "65ch" "screen" "100vw"})
|
||||||
|
|
||||||
|
;; Responsive breakpoints (mobile-first min-width)
|
||||||
|
(define cssx-breakpoints
|
||||||
|
{"sm" "640px"
|
||||||
|
"md" "768px"
|
||||||
|
"lg" "1024px"
|
||||||
|
"xl" "1280px"
|
||||||
|
"2xl" "1536px"})
|
||||||
|
|
||||||
|
;; Pseudo-class states
|
||||||
|
(define cssx-states
|
||||||
|
{"hover" ":hover"
|
||||||
|
"focus" ":focus"
|
||||||
|
"active" ":active"
|
||||||
|
"focus-within" ":focus-within"
|
||||||
|
"focus-visible" ":focus-visible"
|
||||||
|
"first" ":first-child"
|
||||||
|
"last" ":last-child"})
|
||||||
|
|
||||||
|
;; =========================================================================
|
||||||
|
;; Utility resolver — token string → CSS declarations
|
||||||
|
;; =========================================================================
|
||||||
|
|
||||||
|
;; Spacing value: number → rem, "auto" → "auto", "px" → "1px"
|
||||||
|
(define cssx-spacing-value
|
||||||
|
(fn (v)
|
||||||
|
(cond
|
||||||
|
(= v "auto") "auto"
|
||||||
|
(= v "px") "1px"
|
||||||
|
(= v "0") "0px"
|
||||||
|
true (let ((n (parse-int v nil)))
|
||||||
|
(if (nil? n) nil
|
||||||
|
(str (* n 0.25) "rem"))))))
|
||||||
|
|
||||||
|
;; Replace {v} in a template string with a value
|
||||||
|
(define cssx-template
|
||||||
|
(fn (tmpl v)
|
||||||
|
(let ((i (index-of tmpl "{v}")))
|
||||||
|
(if (< i 0) tmpl
|
||||||
|
(let ((result (str (substring tmpl 0 i) v (substring tmpl (+ i 3) (len tmpl)))))
|
||||||
|
;; Handle templates with multiple {v} (e.g. padding-left:{v};padding-right:{v})
|
||||||
|
(let ((j (index-of result "{v}")))
|
||||||
|
(if (< j 0) result
|
||||||
|
(str (substring result 0 j) v (substring result (+ j 3) (len result))))))))))
|
||||||
|
|
||||||
|
;; Resolve a base utility token (no state/bp prefix) → CSS declaration string or nil.
|
||||||
|
;; Tries matchers in order: colour, text-size, text-align, font, spacing, display, max-w, rounded, opacity.
|
||||||
|
(define cssx-resolve
|
||||||
|
(fn (token)
|
||||||
|
(let ((parts (split token "-")))
|
||||||
|
(if (empty? parts) nil
|
||||||
|
(let ((head (first parts))
|
||||||
|
(rest (slice parts 1)))
|
||||||
|
(cond
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Colour utilities: bg-{colour}-{shade}, text-{colour}-{shade}, border-{colour}-{shade}
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (get cssx-colour-props head)
|
||||||
|
(>= (len rest) 2)
|
||||||
|
(not (nil? (parse-int (last rest) nil)))
|
||||||
|
(not (nil? (get colour-bases (join "-" (slice rest 0 (- (len rest) 1)))))))
|
||||||
|
(let ((css-prop (get cssx-colour-props head))
|
||||||
|
(cname (join "-" (slice rest 0 (- (len rest) 1))))
|
||||||
|
(shade (parse-int (last rest) 0)))
|
||||||
|
(str css-prop ":" (colour cname shade)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Text size: text-{size-name} (e.g. text-xl, text-2xl)
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "text")
|
||||||
|
(= (len rest) 1)
|
||||||
|
(not (nil? (get cssx-sizes (first rest)))))
|
||||||
|
(get cssx-sizes (first rest))
|
||||||
|
|
||||||
|
;; Also handle text-2xl etc where rest might be ("2xl") — covered above
|
||||||
|
;; But also "text-sm" etc — covered above since rest is ("sm")
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Text alignment: text-left, text-center, text-right, text-justify
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "text")
|
||||||
|
(= (len rest) 1)
|
||||||
|
(get cssx-alignments (first rest)))
|
||||||
|
(str "text-align:" (first rest))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Font weight: font-bold, font-semibold, etc.
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "font")
|
||||||
|
(= (len rest) 1)
|
||||||
|
(not (nil? (get cssx-weights (first rest)))))
|
||||||
|
(str "font-weight:" (get cssx-weights (first rest)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Font family: font-sans, font-serif, font-mono
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "font")
|
||||||
|
(= (len rest) 1)
|
||||||
|
(not (nil? (get cssx-families (first rest)))))
|
||||||
|
(str "font-family:" (get cssx-families (first rest)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Spacing: p-4, px-2, mt-8, mx-auto, etc.
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (get cssx-spacing-props head)
|
||||||
|
(= (len rest) 1))
|
||||||
|
(let ((tmpl (get cssx-spacing-props head))
|
||||||
|
(v (cssx-spacing-value (first rest))))
|
||||||
|
(if (nil? v) nil (cssx-template tmpl v)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Display: block, flex, grid, hidden, inline, inline-block
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= (len parts) 1)
|
||||||
|
(not (nil? (get cssx-displays head))))
|
||||||
|
(str "display:" (get cssx-displays head))
|
||||||
|
|
||||||
|
;; Inline-block, inline-flex (multi-word)
|
||||||
|
(and (= (len parts) 2)
|
||||||
|
(not (nil? (get cssx-displays token))))
|
||||||
|
(str "display:" (get cssx-displays token))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Max-width: max-w-xl, max-w-3xl, max-w-prose
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "max")
|
||||||
|
(>= (len rest) 2)
|
||||||
|
(= (first rest) "w"))
|
||||||
|
(let ((val-name (join "-" (slice rest 1)))
|
||||||
|
(val (get cssx-max-widths val-name)))
|
||||||
|
(if (nil? val) nil (str "max-width:" val)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Rounded: rounded, rounded-lg, rounded-full, etc.
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(= head "rounded")
|
||||||
|
(cond
|
||||||
|
(empty? rest) "border-radius:0.25rem"
|
||||||
|
(= (first rest) "none") "border-radius:0"
|
||||||
|
(= (first rest) "sm") "border-radius:0.125rem"
|
||||||
|
(= (first rest) "md") "border-radius:0.375rem"
|
||||||
|
(= (first rest) "lg") "border-radius:0.5rem"
|
||||||
|
(= (first rest) "xl") "border-radius:0.75rem"
|
||||||
|
(= (first rest) "2xl") "border-radius:1rem"
|
||||||
|
(= (first rest) "3xl") "border-radius:1.5rem"
|
||||||
|
(= (first rest) "full") "border-radius:9999px"
|
||||||
|
true nil)
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Opacity: opacity-{n} (0-100)
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "opacity")
|
||||||
|
(= (len rest) 1))
|
||||||
|
(let ((n (parse-int (first rest) nil)))
|
||||||
|
(if (nil? n) nil (str "opacity:" (/ n 100))))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Width/height: w-{n}, h-{n}, w-full, h-full, h-screen
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (or (= head "w") (= head "h"))
|
||||||
|
(= (len rest) 1))
|
||||||
|
(let ((prop (if (= head "w") "width" "height"))
|
||||||
|
(val (first rest)))
|
||||||
|
(cond
|
||||||
|
(= val "full") (str prop ":100%")
|
||||||
|
(= val "screen") (str prop (if (= head "w") ":100vw" ":100vh"))
|
||||||
|
(= val "auto") (str prop ":auto")
|
||||||
|
(= val "min") (str prop ":min-content")
|
||||||
|
(= val "max") (str prop ":max-content")
|
||||||
|
(= val "fit") (str prop ":fit-content")
|
||||||
|
true (let ((n (parse-int val nil)))
|
||||||
|
(if (nil? n) nil
|
||||||
|
(str prop ":" (* n 0.25) "rem")))))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Gap: gap-{n}
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "gap")
|
||||||
|
(= (len rest) 1))
|
||||||
|
(let ((v (cssx-spacing-value (first rest))))
|
||||||
|
(if (nil? v) nil (str "gap:" v)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Text decoration: underline, no-underline, line-through
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= (len parts) 1)
|
||||||
|
(or (= head "underline") (= head "overline") (= head "line-through")))
|
||||||
|
(str "text-decoration-line:" head)
|
||||||
|
|
||||||
|
(and (= (len parts) 2) (= head "no") (= (first rest) "underline"))
|
||||||
|
"text-decoration-line:none"
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Cursor: cursor-pointer, cursor-default, etc.
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "cursor") (= (len rest) 1))
|
||||||
|
(str "cursor:" (first rest))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Overflow: overflow-hidden, overflow-auto, etc.
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "overflow") (= (len rest) 1))
|
||||||
|
(str "overflow:" (first rest))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Transition: transition, transition-colors, etc.
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
(and (= head "transition") (empty? rest))
|
||||||
|
"transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||||
|
|
||||||
|
(and (= head "transition") (= (first rest) "colors"))
|
||||||
|
"transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
;; Fallback: unrecognised → nil (skip)
|
||||||
|
;; ---------------------------------------------------------
|
||||||
|
true nil))))))
|
||||||
|
|
||||||
|
;; =========================================================================
|
||||||
|
;; Token processor — full token with optional state/bp prefixes
|
||||||
|
;; =========================================================================
|
||||||
|
|
||||||
|
;; Process one token string → {:cls "sx-..." :rule ".sx-...{...}"} or nil
|
||||||
|
;; Token format: [bp:]?[state:]?utility
|
||||||
|
;; Examples: "bg-yellow-199", "hover:bg-rose-500", "md:hover:text-xl"
|
||||||
|
(define cssx-process-token
|
||||||
|
(fn (token)
|
||||||
|
(let ((colon-parts (split token ":"))
|
||||||
|
(n (len colon-parts)))
|
||||||
|
;; Extract state, bp, and base utility from colon-separated parts
|
||||||
|
(let ((bp nil) (state nil) (base nil))
|
||||||
|
;; 1 part: just utility
|
||||||
|
;; 2 parts: modifier:utility (could be bp or state)
|
||||||
|
;; 3 parts: bp:state:utility
|
||||||
|
(cond
|
||||||
|
(= n 1)
|
||||||
|
(do (set! base (first colon-parts)))
|
||||||
|
(= n 2)
|
||||||
|
(let ((prefix (first colon-parts)))
|
||||||
|
(set! base (last colon-parts))
|
||||||
|
(if (not (nil? (get cssx-breakpoints prefix)))
|
||||||
|
(set! bp prefix)
|
||||||
|
(set! state prefix)))
|
||||||
|
(>= n 3)
|
||||||
|
(do
|
||||||
|
(set! bp (first colon-parts))
|
||||||
|
(set! state (nth colon-parts 1))
|
||||||
|
(set! base (last colon-parts))))
|
||||||
|
|
||||||
|
;; Resolve the base utility to CSS declarations
|
||||||
|
(let ((css (cssx-resolve base)))
|
||||||
|
(if (nil? css) nil
|
||||||
|
(let ((cls (str "sx-" (join "-" (split token ":"))))
|
||||||
|
(pseudo (if (nil? state) ""
|
||||||
|
(or (get cssx-states state) (str ":" state))))
|
||||||
|
(decl (str "." cls pseudo "{" css "}")))
|
||||||
|
(if (nil? bp)
|
||||||
|
{"cls" cls "rule" decl}
|
||||||
|
(let ((min-w (or (get cssx-breakpoints bp) bp)))
|
||||||
|
{"cls" cls
|
||||||
|
"rule" (str "@media(min-width:" min-w "){" decl "}")})))))))))
|
||||||
|
|
||||||
|
;; =========================================================================
|
||||||
|
;; tw — Tailwind-style inline style string from utility tokens
|
||||||
|
;;
|
||||||
|
;; Same token format as ~cssx/tw, returns a CSS declaration string for :style.
|
||||||
|
;; Cannot do hover/responsive (use ~cssx/tw for those).
|
||||||
|
;;
|
||||||
|
;; Usage:
|
||||||
|
;; (div :style (tw "bg-yellow-199 text-violet-700 p-4 font-bold")
|
||||||
|
;; "content")
|
||||||
|
;; =========================================================================
|
||||||
|
|
||||||
|
(define tw
|
||||||
|
(fn (tokens-str)
|
||||||
|
(let ((tokens (split (or tokens-str "") " "))
|
||||||
|
(parts (list)))
|
||||||
|
(for-each (fn (tok)
|
||||||
|
(when (not (= tok ""))
|
||||||
|
(let ((css (cssx-resolve tok)))
|
||||||
|
(when (not (nil? css))
|
||||||
|
(append! parts (str css ";"))))))
|
||||||
|
tokens)
|
||||||
|
(join "" parts))))
|
||||||
|
|
||||||
|
;; =========================================================================
|
||||||
|
;; ~cssx/tw — spread component that injects JIT classes onto parent element
|
||||||
|
;;
|
||||||
|
;; Usage — as a child of any element:
|
||||||
|
;; (div (~cssx/tw "bg-yellow-199 text-violet-700 p-4 font-bold")
|
||||||
|
;; (h1 "styled content"))
|
||||||
|
;;
|
||||||
|
;; (button (~cssx/tw "hover:bg-rose-500 focus:border-blue-400")
|
||||||
|
;; "interactive")
|
||||||
|
;;
|
||||||
|
;; Returns a spread value that merges :class and :data-tw onto the parent
|
||||||
|
;; element. Collects CSS rules into the "cssx" bucket for a single global
|
||||||
|
;; <style> flush. No wrapper element, no per-element <style> tags.
|
||||||
|
;;
|
||||||
|
;; Reusable as variables:
|
||||||
|
;; (define important (~cssx/tw "font-bold text-4xl"))
|
||||||
|
;; (div important "the queen is dead")
|
||||||
|
;;
|
||||||
|
;; Multiple spreads merge naturally:
|
||||||
|
;; (div (~cssx/tw "bg-red-500") (~cssx/tw "p-4") "content")
|
||||||
|
;; =========================================================================
|
||||||
|
|
||||||
|
(defcomp ~cssx/tw (tokens)
|
||||||
|
(let ((token-list (filter (fn (t) (not (= t "")))
|
||||||
|
(split (or tokens "") " ")))
|
||||||
|
(results (map cssx-process-token token-list))
|
||||||
|
(valid (filter (fn (r) (not (nil? r))) results))
|
||||||
|
(classes (map (fn (r) (get r "cls")) valid))
|
||||||
|
(rules (map (fn (r) (get r "rule")) valid))
|
||||||
|
(_ (for-each (fn (rule) (collect! "cssx" rule)) rules)))
|
||||||
|
;; Return spread: injects class + data-tw onto parent element
|
||||||
|
(if (empty? classes)
|
||||||
|
nil
|
||||||
|
(make-spread {"class" (join " " classes)
|
||||||
|
"data-tw" (or tokens "")}))))
|
||||||
|
|
||||||
|
|
||||||
|
;; =========================================================================
|
||||||
|
;; ~cssx/flush — emit collected CSS rules as a single <style> tag
|
||||||
|
;;
|
||||||
|
;; Place once in the page (typically in the layout, before </body>).
|
||||||
|
;; Emits all accumulated CSSX rules and clears the bucket.
|
||||||
|
;;
|
||||||
|
;; Usage:
|
||||||
|
;; (~cssx/flush)
|
||||||
|
;; =========================================================================
|
||||||
|
|
||||||
|
(defcomp ~cssx/flush ()
|
||||||
|
(let ((rules (collected "cssx")))
|
||||||
|
(clear-collected! "cssx")
|
||||||
|
(when (not (empty? rules))
|
||||||
|
(raw! (str "<style data-cssx>" (join "" rules) "</style>")))))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
;; Nav components — logo header, sibling arrows, children links
|
;; Nav components — logo header, sibling arrows, children links
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
;; CSSX replaces Tailwind text-*/bg-*/font-* classes — computed via cssx.sx
|
;; Styling via cssx-style utility tokens (cssx.sx) — same format as ~cssx/tw
|
||||||
|
|
||||||
;; Logo + tagline + copyright — always shown at top of page area.
|
;; Logo + tagline + copyright — always shown at top of page area.
|
||||||
;; The header itself is an island so the "reactive" word can cycle colours
|
;; The header itself is an island so the "reactive" word can cycle colours
|
||||||
@@ -21,23 +21,21 @@
|
|||||||
(shade (signal 500))
|
(shade (signal 500))
|
||||||
(current-family (computed (fn ()
|
(current-family (computed (fn ()
|
||||||
(nth families (mod (deref idx) (len families)))))))
|
(nth families (mod (deref idx) (len families)))))))
|
||||||
(div :style (str (display "block") (max-w (get cssx-max-widths "3xl"))
|
(div :style (tw "block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center")
|
||||||
(mx-auto) (px 4) (pt 8) (pb 4) (align "center"))
|
|
||||||
;; Logo — only this navigates home
|
;; Logo — only this navigates home
|
||||||
(a :href "/sx/"
|
(a :href "/sx/"
|
||||||
:sx-get "/sx/" :sx-target "#main-panel" :sx-select "#main-panel"
|
:sx-get "/sx/" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||||
:sx-swap "outerHTML" :sx-push-url "true"
|
:sx-swap "outerHTML" :sx-push-url "true"
|
||||||
:style (str (display "block") (decoration "none"))
|
:style (tw "block no-underline")
|
||||||
(lake :id "logo"
|
(lake :id "logo"
|
||||||
(span :style (str (display "block") (mb 2)
|
(span :style (tw "block mb-2 text-violet-699 text-4xl font-bold font-mono")
|
||||||
(cssx (:text (colour "violet" 699) (size "4xl") (weight "bold") (family "mono"))))
|
|
||||||
"(<sx>)")))
|
"(<sx>)")))
|
||||||
;; Tagline — clicking "reactive" cycles colour.
|
;; Tagline — clicking "reactive" cycles colour.
|
||||||
(p :style (str (mb 1) (cssx (:text (colour "stone" 500) (size "lg"))))
|
(p :style (tw "mb-1 text-stone-500 text-lg")
|
||||||
"The framework-free "
|
"The framework-free "
|
||||||
(span
|
(span
|
||||||
:style (str (cssx (:text (colour (deref current-family) (deref shade))
|
:style (str "color:" (colour (deref current-family) (deref shade)) ";"
|
||||||
(weight "bold")))
|
(tw "font-bold")
|
||||||
"cursor:pointer;transition:color 0.3s,font-weight 0.3s;")
|
"cursor:pointer;transition:color 0.3s,font-weight 0.3s;")
|
||||||
:on-click (fn (e)
|
:on-click (fn (e)
|
||||||
(batch (fn ()
|
(batch (fn ()
|
||||||
@@ -47,11 +45,10 @@
|
|||||||
" hypermedium")
|
" hypermedium")
|
||||||
;; Lake: server morphs copyright on navigation without disturbing signals.
|
;; Lake: server morphs copyright on navigation without disturbing signals.
|
||||||
(lake :id "copyright"
|
(lake :id "copyright"
|
||||||
(p :style (cssx (:text (colour "stone" 400) (size "xs")))
|
(p :style (tw "text-stone-400 text-xs")
|
||||||
"© Giles Bradshaw 2026"
|
"© Giles Bradshaw 2026"
|
||||||
(when path
|
(when path
|
||||||
(span :style (str (cssx (:text (colour "stone" 300) (size "xs")))
|
(span :style (str (tw "text-stone-300 text-xs") "margin-left:0.5em;")
|
||||||
"margin-left:0.5em;")
|
|
||||||
(str "· " path))))))))
|
(str "· " path))))))))
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +77,7 @@
|
|||||||
:sx-select "#main-panel" :sx-swap "outerHTML"
|
:sx-select "#main-panel" :sx-swap "outerHTML"
|
||||||
:sx-push-url "true"
|
:sx-push-url "true"
|
||||||
:class "text-right"
|
:class "text-right"
|
||||||
:style (cssx (:text (colour "stone" 500) (size "sm")))
|
:style (tw "text-stone-500 text-sm")
|
||||||
(str "← " (get prev-node "label")))
|
(str "← " (get prev-node "label")))
|
||||||
(a :href (get node "href")
|
(a :href (get node "href")
|
||||||
:sx-get (get node "href") :sx-target "#main-panel"
|
:sx-get (get node "href") :sx-target "#main-panel"
|
||||||
@@ -88,15 +85,15 @@
|
|||||||
:sx-push-url "true"
|
:sx-push-url "true"
|
||||||
:class "text-center px-4"
|
:class "text-center px-4"
|
||||||
:style (if is-leaf
|
:style (if is-leaf
|
||||||
(cssx (:text (colour "violet" 700) (size "2xl") (weight "bold")))
|
(tw "text-violet-700 text-2xl font-bold")
|
||||||
(cssx (:text (colour "violet" 700) (size "lg") (weight "semibold"))))
|
(tw "text-violet-700 text-lg font-semibold"))
|
||||||
(get node "label"))
|
(get node "label"))
|
||||||
(a :href (get next-node "href")
|
(a :href (get next-node "href")
|
||||||
:sx-get (get next-node "href") :sx-target "#main-panel"
|
:sx-get (get next-node "href") :sx-target "#main-panel"
|
||||||
:sx-select "#main-panel" :sx-swap "outerHTML"
|
:sx-select "#main-panel" :sx-swap "outerHTML"
|
||||||
:sx-push-url "true"
|
:sx-push-url "true"
|
||||||
:class "text-left"
|
:class "text-left"
|
||||||
:style (cssx (:text (colour "stone" 500) (size "sm")))
|
:style (tw "text-stone-500 text-sm")
|
||||||
(str (get next-node "label") " →")))))))
|
(str (get next-node "label") " →")))))))
|
||||||
|
|
||||||
;; Children links — shown as clearly clickable buttons.
|
;; Children links — shown as clearly clickable buttons.
|
||||||
@@ -109,8 +106,7 @@
|
|||||||
:sx-select "#main-panel" :sx-swap "outerHTML"
|
:sx-select "#main-panel" :sx-swap "outerHTML"
|
||||||
:sx-push-url "true"
|
:sx-push-url "true"
|
||||||
:class "px-3 py-1.5 rounded border transition-colors"
|
:class "px-3 py-1.5 rounded border transition-colors"
|
||||||
:style (cssx (:text (colour "violet" 700) (size "sm"))
|
:style (tw "text-violet-700 text-sm border-violet-200")
|
||||||
(:border (colour "violet" 200)))
|
|
||||||
(get item "label")))
|
(get item "label")))
|
||||||
items))))
|
items))))
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,18 @@
|
|||||||
|
|
||||||
(defcomp ~not-found/content (&key (path :as string?))
|
(defcomp ~not-found/content (&key (path :as string?))
|
||||||
(div :class "max-w-3xl mx-auto px-4 py-12 text-center"
|
(div :class "max-w-3xl mx-auto px-4 py-12 text-center"
|
||||||
(h1 :style (cssx (:text (colour "stone" 800) (size "3xl") (weight "bold")))
|
(h1 :style (tw "text-stone-800 text-3xl font-bold")
|
||||||
"404")
|
"404")
|
||||||
(p :class "mt-4"
|
(p :class "mt-4"
|
||||||
:style (cssx (:text (colour "stone" 500) (size "lg")))
|
:style (tw "text-stone-500 text-lg")
|
||||||
"Page not found")
|
"Page not found")
|
||||||
(when path
|
(when path
|
||||||
(p :class "mt-2"
|
(p :class "mt-2"
|
||||||
:style (cssx (:text (colour "stone" 400) (size "sm") (family "mono")))
|
:style (tw "text-stone-400 text-sm font-mono")
|
||||||
path))
|
path))
|
||||||
(a :href "/sx/"
|
(a :href "/sx/"
|
||||||
:sx-get "/sx/" :sx-target "#main-panel" :sx-select "#main-panel"
|
:sx-get "/sx/" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||||
:sx-swap "outerHTML" :sx-push-url "true"
|
:sx-swap "outerHTML" :sx-push-url "true"
|
||||||
:class "inline-block mt-6 px-4 py-2 rounded border transition-colors"
|
:class "inline-block mt-6 px-4 py-2 rounded border transition-colors"
|
||||||
:style (cssx (:text (colour "violet" 700) (size "sm"))
|
:style (tw "text-violet-700 text-sm border-violet-200")
|
||||||
(:border (colour "violet" 200)))
|
|
||||||
"Back to home")))
|
"Back to home")))
|
||||||
|
|||||||
Reference in New Issue
Block a user