diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 4dc5628..b4e8411 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-08T16:12:12Z"; + var SX_VERSION = "2026-03-08T16:24:34Z"; 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(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 == "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))))))))))); 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"]; + 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"]; // 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 == "for-each")) ? (function() { +})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(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,6 +1755,32 @@ 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)); + var target = domQuery(selector); + return (isSxTruthy(!isSxTruthy(target)) ? (logWarn((String("Portal target not found: ") + String(selector))), createComment((String("portal: ") + String(selector) + String(" (not found)")))) : (function() { + var marker = createComment((String("portal: ") + String(selector))); + var frag = createFragment(); + { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } } + (function() { + var portalNodes = domChildNodes(frag); + domAppend(target, frag); + return registerInScope(function() { return forEach(function(n) { return domRemove(n); }, portalNodes); }); +})(); + return marker; +})()); +})(); }; + // === Transpiled from engine === @@ -3014,6 +3040,15 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { // register-in-scope var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); }; + // ref + var ref = function(initial) { return {["current"]: initial}; }; + + // ref-get + var refGet = function(r) { return get(r, "current"); }; + + // ref-set! + var refSet_b = function(r, v) { return dictSet(r, "current", v); }; + // *store-registry* var _storeRegistry = {}; @@ -3775,6 +3810,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { // --- Events --- 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 elementValue(el) { return el && el.value !== undefined ? el.value : NIL; } function domAddListener(el, event, fn, opts) { diff --git a/shared/static/scripts/sx-ref.js b/shared/static/scripts/sx-ref.js index 4dc5628..b4e8411 100644 --- a/shared/static/scripts/sx-ref.js +++ b/shared/static/scripts/sx-ref.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-08T16:12:12Z"; + var SX_VERSION = "2026-03-08T16:24:34Z"; 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(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 == "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))))))))))); 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"]; + 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"]; // 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 == "for-each")) ? (function() { +})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(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,6 +1755,32 @@ 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)); + var target = domQuery(selector); + return (isSxTruthy(!isSxTruthy(target)) ? (logWarn((String("Portal target not found: ") + String(selector))), createComment((String("portal: ") + String(selector) + String(" (not found)")))) : (function() { + var marker = createComment((String("portal: ") + String(selector))); + var frag = createFragment(); + { var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } } + (function() { + var portalNodes = domChildNodes(frag); + domAppend(target, frag); + return registerInScope(function() { return forEach(function(n) { return domRemove(n); }, portalNodes); }); +})(); + return marker; +})()); +})(); }; + // === Transpiled from engine === @@ -3014,6 +3040,15 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { // register-in-scope var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); }; + // ref + var ref = function(initial) { return {["current"]: initial}; }; + + // ref-get + var refGet = function(r) { return get(r, "current"); }; + + // ref-set! + var refSet_b = function(r, v) { return dictSet(r, "current", v); }; + // *store-registry* var _storeRegistry = {}; @@ -3775,6 +3810,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { // --- Events --- 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 elementValue(el) { return el && el.value !== undefined ? el.value : NIL; } function domAddListener(el, event, fn, opts) { diff --git a/shared/sx/helpers.py b/shared/sx/helpers.py index 813f5ad..f3b2044 100644 --- a/shared/sx/helpers.py +++ b/shared/sx/helpers.py @@ -662,6 +662,7 @@ details.group{{overflow:hidden}}details.group>summary{{list-style:none}}details. +