Add reactive spreads — signal-driven attribute injection in islands
When a spread value (e.g. from ~cssx/tw) appears inside an island with signal-dependent tokens, reactive-spread tracks deps and updates the element's class/attrs when signals change. Old classes are surgically removed, new ones appended, and freshly collected CSS rules are flushed to the live stylesheet. Multiple reactive spreads on one element are safe. 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-13T04:50:39Z";
|
||||
var SX_VERSION = "2026-03-13T05:11:12Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -1934,7 +1934,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
return assoc(state, "skip", true, "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() {
|
||||
return (isSxTruthy((isSxTruthy(isSpread(child)) && _islandScope)) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? forEach(function(key) { return (function() {
|
||||
var val = dictGet(spreadAttrs(child), key);
|
||||
return (isSxTruthy((key == "class")) ? (function() {
|
||||
var existing = domGetAttr(el, "class");
|
||||
@@ -1943,7 +1943,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
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));
|
||||
})(); }, keys(spreadAttrs(child))) : domAppend(el, child)));
|
||||
})() : NIL), assoc(state, "i", (get(state, "i") + 1)))));
|
||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||
return el;
|
||||
@@ -2269,6 +2269,44 @@ return effect(function() { return (function() {
|
||||
})();
|
||||
})(); }); };
|
||||
|
||||
// reactive-spread
|
||||
var reactiveSpread = function(el, renderFn) { return (function() {
|
||||
var prevClasses = [];
|
||||
var prevExtraKeys = [];
|
||||
(function() {
|
||||
var existing = sxOr(domGetAttr(el, "data-sx-reactive-attrs"), "");
|
||||
return domSetAttr(el, "data-sx-reactive-attrs", (isSxTruthy(isEmpty(existing)) ? "_spread" : (String(existing) + String(",_spread"))));
|
||||
})();
|
||||
return effect(function() { if (isSxTruthy(!isSxTruthy(isEmpty(prevClasses)))) {
|
||||
(function() {
|
||||
var current = sxOr(domGetAttr(el, "class"), "");
|
||||
var tokens = filter(function(c) { return !isSxTruthy((c == "")); }, split(current, " "));
|
||||
var kept = filter(function(c) { return !isSxTruthy(some(function(pc) { return (pc == c); }, prevClasses)); }, tokens);
|
||||
return (isSxTruthy(isEmpty(kept)) ? domRemoveAttr(el, "class") : domSetAttr(el, "class", join(" ", kept)));
|
||||
})();
|
||||
}
|
||||
{ var _c = prevExtraKeys; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; domRemoveAttr(el, k); } }
|
||||
return (function() {
|
||||
var result = renderFn();
|
||||
return (isSxTruthy(isSpread(result)) ? (function() {
|
||||
var attrs = spreadAttrs(result);
|
||||
var clsStr = sxOr(dictGet(attrs, "class"), "");
|
||||
var newClasses = filter(function(c) { return !isSxTruthy((c == "")); }, split(clsStr, " "));
|
||||
var extraKeys = filter(function(k) { return !isSxTruthy((k == "class")); }, keys(attrs));
|
||||
prevClasses = newClasses;
|
||||
prevExtraKeys = extraKeys;
|
||||
if (isSxTruthy(!isSxTruthy(isEmpty(newClasses)))) {
|
||||
(function() {
|
||||
var current = sxOr(domGetAttr(el, "class"), "");
|
||||
return domSetAttr(el, "class", (isSxTruthy((isSxTruthy(current) && !isSxTruthy((current == "")))) ? (String(current) + String(" ") + String(clsStr)) : clsStr));
|
||||
})();
|
||||
}
|
||||
{ var _c = extraKeys; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; domSetAttr(el, k, (String(dictGet(attrs, k)))); } }
|
||||
return flushCssxToDom();
|
||||
})() : ((prevClasses = []), (prevExtraKeys = [])));
|
||||
})(); });
|
||||
})(); };
|
||||
|
||||
// reactive-fragment
|
||||
var reactiveFragment = function(testFn, renderFn, env, ns) { return (function() {
|
||||
var marker = createComment("island-fragment");
|
||||
|
||||
Reference in New Issue
Block a user