Remove reactive class/style (CSSX covers it), add error boundaries + resource
Reactive class/style (:class-map, :style-map) removed — CSSX components already handle dynamic class/style via defcomp with full SX logic. Added: - error-boundary render-dom form: try/catch around children, renders fallback fn with (err retry) on failure, disposes partial effects - resource async signal: wraps promise into signal with loading/data/error states, transitions automatically on resolve/reject - try-catch, error-message, promise-then platform functions - Updated Phase 2 status tables and demo page numbering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-08T16:24:34Z";
|
||||
var SX_VERSION = "2026-03-08T16:35:00Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -1492,7 +1492,7 @@ return result; }, args);
|
||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
||||
var attrName = keywordName(arg);
|
||||
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? domListen(el, slice(attrName, 3), attrVal) : (isSxTruthy((isSxTruthy((attrName == "bind")) && isSignal(attrVal))) ? bindInput(el, attrVal) : (isSxTruthy((attrName == "class-map")) ? reactiveClassMap(el, attrVal) : (isSxTruthy((attrName == "style-map")) ? reactiveStyleMap(el, attrVal) : (isSxTruthy((attrName == "ref")) ? refSet_b(attrVal, el) : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))))))));
|
||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? domListen(el, slice(attrName, 3), attrVal) : (isSxTruthy((isSxTruthy((attrName == "bind")) && isSignal(attrVal))) ? bindInput(el, attrVal) : (isSxTruthy((attrName == "ref")) ? refSet_b(attrVal, el) : (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));
|
||||
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||
@@ -1546,7 +1546,7 @@ return result; }, args);
|
||||
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
|
||||
|
||||
// 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"];
|
||||
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"];
|
||||
|
||||
// render-dom-form?
|
||||
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
||||
@@ -1604,7 +1604,7 @@ return result; }, args);
|
||||
return domAppend(frag, val);
|
||||
})(); }, coll);
|
||||
return frag;
|
||||
})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(args, env, ns) : (isSxTruthy((name == "for-each")) ? (function() {
|
||||
})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(args, env, ns) : (isSxTruthy((name == "error-boundary")) ? renderDomErrorBoundary(args, env, ns) : (isSxTruthy((name == "for-each")) ? (function() {
|
||||
var f = trampoline(evalExpr(nth(expr, 1), env));
|
||||
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
||||
var frag = createFragment();
|
||||
@@ -1613,7 +1613,7 @@ return result; }, args);
|
||||
return domAppend(frag, val);
|
||||
})(); } }
|
||||
return frag;
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))); };
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); };
|
||||
|
||||
// render-lambda-dom
|
||||
var renderLambdaDom = function(f, args, env, ns) { return (function() {
|
||||
@@ -1755,15 +1755,6 @@ return (isSxTruthy(testFn()) ? (function() {
|
||||
return domListen(el, (isSxTruthy(isCheckbox) ? "change" : "input"), function(e) { return (isSxTruthy(isCheckbox) ? reset_b(sig, domGetProp(el, "checked")) : reset_b(sig, domGetProp(el, "value"))); });
|
||||
})(); };
|
||||
|
||||
// reactive-class-map
|
||||
var reactiveClassMap = function(el, classDict) { return effect(function() { return forEach(function(cls) { return (function() {
|
||||
var val = deref(get(classDict, cls));
|
||||
return (isSxTruthy(val) ? domAddClass(el, cls) : domRemoveClass(el, cls));
|
||||
})(); }, keys(classDict)); }); };
|
||||
|
||||
// reactive-style-map
|
||||
var reactiveStyleMap = function(el, styleDict) { return effect(function() { return forEach(function(prop) { return domSetStyle(el, prop, (String(deref(get(styleDict, prop))))); }, keys(styleDict)); }); };
|
||||
|
||||
// render-dom-portal
|
||||
var renderDomPortal = function(args, env, ns) { return (function() {
|
||||
var selector = trampoline(evalExpr(first(args), env));
|
||||
@@ -1781,6 +1772,39 @@ return (isSxTruthy(testFn()) ? (function() {
|
||||
})());
|
||||
})(); };
|
||||
|
||||
// render-dom-error-boundary
|
||||
var renderDomErrorBoundary = function(args, env, ns) { return (function() {
|
||||
var fallbackExpr = first(args);
|
||||
var bodyExprs = rest(args);
|
||||
var container = domCreateElement("div", NIL);
|
||||
var boundaryDisposers = [];
|
||||
domSetAttr(container, "data-sx-boundary", "true");
|
||||
return (function() {
|
||||
var renderBody = function() { { var _c = boundaryDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; d(); } }
|
||||
boundaryDisposers = [];
|
||||
domSetProp(container, "innerHTML", "");
|
||||
return tryCatch(function() { return withIslandScope(function(disposable) { boundaryDisposers.push(disposable);
|
||||
return registerInScope(disposable); }, function() { return (function() {
|
||||
var frag = createFragment();
|
||||
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
|
||||
return domAppend(container, frag);
|
||||
})(); }); }, function(err) { { var _c = boundaryDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; d(); } }
|
||||
boundaryDisposers = [];
|
||||
return (function() {
|
||||
var fallbackFn = trampoline(evalExpr(fallbackExpr, env));
|
||||
var retryFn = function() { return renderBody(); };
|
||||
return (function() {
|
||||
var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns));
|
||||
return domAppend(container, fallbackDom);
|
||||
})();
|
||||
})(); }); };
|
||||
renderBody();
|
||||
registerInScope(function() { { var _c = boundaryDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; d(); } }
|
||||
return (boundaryDisposers = []); });
|
||||
return container;
|
||||
})();
|
||||
})(); };
|
||||
|
||||
|
||||
// === Transpiled from engine ===
|
||||
|
||||
@@ -3083,6 +3107,13 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
return remove;
|
||||
})(); }); };
|
||||
|
||||
// resource
|
||||
var resource = function(fetchFn) { return (function() {
|
||||
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
|
||||
promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
|
||||
return state;
|
||||
})(); };
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Platform interface — DOM adapter (browser-only)
|
||||
@@ -3812,6 +3843,12 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
|
||||
function stopPropagation_(e) { if (e && e.stopPropagation) e.stopPropagation(); }
|
||||
function domFocus(el) { if (el && el.focus) el.focus(); }
|
||||
function tryCatch(tryFn, catchFn) {
|
||||
try { return tryFn(); } catch (e) { return catchFn(e); }
|
||||
}
|
||||
function errorMessage(e) {
|
||||
return e && e.message ? e.message : String(e);
|
||||
}
|
||||
function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; }
|
||||
|
||||
function domAddListener(el, event, fn, opts) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-08T16:24:34Z";
|
||||
var SX_VERSION = "2026-03-08T16:35:00Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -1492,7 +1492,7 @@ return result; }, args);
|
||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
||||
var attrName = keywordName(arg);
|
||||
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? domListen(el, slice(attrName, 3), attrVal) : (isSxTruthy((isSxTruthy((attrName == "bind")) && isSignal(attrVal))) ? bindInput(el, attrVal) : (isSxTruthy((attrName == "class-map")) ? reactiveClassMap(el, attrVal) : (isSxTruthy((attrName == "style-map")) ? reactiveStyleMap(el, attrVal) : (isSxTruthy((attrName == "ref")) ? refSet_b(attrVal, el) : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))))))));
|
||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? domListen(el, slice(attrName, 3), attrVal) : (isSxTruthy((isSxTruthy((attrName == "bind")) && isSignal(attrVal))) ? bindInput(el, attrVal) : (isSxTruthy((attrName == "ref")) ? refSet_b(attrVal, el) : (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));
|
||||
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||
@@ -1546,7 +1546,7 @@ return result; }, args);
|
||||
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
|
||||
|
||||
// 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"];
|
||||
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"];
|
||||
|
||||
// render-dom-form?
|
||||
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
||||
@@ -1604,7 +1604,7 @@ return result; }, args);
|
||||
return domAppend(frag, val);
|
||||
})(); }, coll);
|
||||
return frag;
|
||||
})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(args, env, ns) : (isSxTruthy((name == "for-each")) ? (function() {
|
||||
})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(args, env, ns) : (isSxTruthy((name == "error-boundary")) ? renderDomErrorBoundary(args, env, ns) : (isSxTruthy((name == "for-each")) ? (function() {
|
||||
var f = trampoline(evalExpr(nth(expr, 1), env));
|
||||
var coll = trampoline(evalExpr(nth(expr, 2), env));
|
||||
var frag = createFragment();
|
||||
@@ -1613,7 +1613,7 @@ return result; }, args);
|
||||
return domAppend(frag, val);
|
||||
})(); } }
|
||||
return frag;
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))); };
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); };
|
||||
|
||||
// render-lambda-dom
|
||||
var renderLambdaDom = function(f, args, env, ns) { return (function() {
|
||||
@@ -1755,15 +1755,6 @@ return (isSxTruthy(testFn()) ? (function() {
|
||||
return domListen(el, (isSxTruthy(isCheckbox) ? "change" : "input"), function(e) { return (isSxTruthy(isCheckbox) ? reset_b(sig, domGetProp(el, "checked")) : reset_b(sig, domGetProp(el, "value"))); });
|
||||
})(); };
|
||||
|
||||
// reactive-class-map
|
||||
var reactiveClassMap = function(el, classDict) { return effect(function() { return forEach(function(cls) { return (function() {
|
||||
var val = deref(get(classDict, cls));
|
||||
return (isSxTruthy(val) ? domAddClass(el, cls) : domRemoveClass(el, cls));
|
||||
})(); }, keys(classDict)); }); };
|
||||
|
||||
// reactive-style-map
|
||||
var reactiveStyleMap = function(el, styleDict) { return effect(function() { return forEach(function(prop) { return domSetStyle(el, prop, (String(deref(get(styleDict, prop))))); }, keys(styleDict)); }); };
|
||||
|
||||
// render-dom-portal
|
||||
var renderDomPortal = function(args, env, ns) { return (function() {
|
||||
var selector = trampoline(evalExpr(first(args), env));
|
||||
@@ -1781,6 +1772,39 @@ return (isSxTruthy(testFn()) ? (function() {
|
||||
})());
|
||||
})(); };
|
||||
|
||||
// render-dom-error-boundary
|
||||
var renderDomErrorBoundary = function(args, env, ns) { return (function() {
|
||||
var fallbackExpr = first(args);
|
||||
var bodyExprs = rest(args);
|
||||
var container = domCreateElement("div", NIL);
|
||||
var boundaryDisposers = [];
|
||||
domSetAttr(container, "data-sx-boundary", "true");
|
||||
return (function() {
|
||||
var renderBody = function() { { var _c = boundaryDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; d(); } }
|
||||
boundaryDisposers = [];
|
||||
domSetProp(container, "innerHTML", "");
|
||||
return tryCatch(function() { return withIslandScope(function(disposable) { boundaryDisposers.push(disposable);
|
||||
return registerInScope(disposable); }, function() { return (function() {
|
||||
var frag = createFragment();
|
||||
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
|
||||
return domAppend(container, frag);
|
||||
})(); }); }, function(err) { { var _c = boundaryDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; d(); } }
|
||||
boundaryDisposers = [];
|
||||
return (function() {
|
||||
var fallbackFn = trampoline(evalExpr(fallbackExpr, env));
|
||||
var retryFn = function() { return renderBody(); };
|
||||
return (function() {
|
||||
var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns));
|
||||
return domAppend(container, fallbackDom);
|
||||
})();
|
||||
})(); }); };
|
||||
renderBody();
|
||||
registerInScope(function() { { var _c = boundaryDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; d(); } }
|
||||
return (boundaryDisposers = []); });
|
||||
return container;
|
||||
})();
|
||||
})(); };
|
||||
|
||||
|
||||
// === Transpiled from engine ===
|
||||
|
||||
@@ -3083,6 +3107,13 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
return remove;
|
||||
})(); }); };
|
||||
|
||||
// resource
|
||||
var resource = function(fetchFn) { return (function() {
|
||||
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
|
||||
promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
|
||||
return state;
|
||||
})(); };
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Platform interface — DOM adapter (browser-only)
|
||||
@@ -3812,6 +3843,12 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
|
||||
function stopPropagation_(e) { if (e && e.stopPropagation) e.stopPropagation(); }
|
||||
function domFocus(el) { if (el && el.focus) el.focus(); }
|
||||
function tryCatch(tryFn, catchFn) {
|
||||
try { return tryFn(); } catch (e) { return catchFn(e); }
|
||||
}
|
||||
function errorMessage(e) {
|
||||
return e && e.message ? e.message : String(e);
|
||||
}
|
||||
function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; }
|
||||
|
||||
function domAddListener(el, event, fn, opts) {
|
||||
|
||||
Reference in New Issue
Block a user