diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index abaa480..faf940b 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-13T03:40:30Z"; + var SX_VERSION = "2026-03-13T04:08:59Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -2947,6 +2947,7 @@ return postSwap(target); }); sxProcessScripts(root); sxHydrate(root); sxHydrateIslands(root); +flushCssxToDom(); return processElements(root); }; // process-settle-hooks @@ -3181,7 +3182,7 @@ return logWarn((String("sx:offline sync failed ") + String(get(entry, "action")) })(); }; // swap-rendered-content - var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); }; + var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), flushCssxToDom(), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); }; // resolve-route-target var resolveRouteTarget = function(targetSel) { return (isSxTruthy((isSxTruthy(targetSel) && !isSxTruthy((targetSel == "true")))) ? domQuery(targetSel) : NIL); }; @@ -3400,7 +3401,8 @@ return processEmitElements(root); }; hoistHeadElementsFull(el); processElements(el); sxHydrateElements(el); - return sxHydrateIslands(el); + sxHydrateIslands(el); + return flushCssxToDom(); })() : NIL); })(); }; @@ -3416,6 +3418,7 @@ return (function() { processElements(el); sxHydrateElements(el); sxHydrateIslands(el); + flushCssxToDom(); return domDispatch(el, "sx:resolved", {"id": id}); })() : logWarn((String("resolveSuspense: no element for id=") + String(id)))); })(); }; @@ -3561,8 +3564,23 @@ callExpr.push(dictGet(kwargs, k)); } } })() : NIL); })() : NIL); }; + // flush-cssx-to-dom + var flushCssxToDom = function() { return (function() { + var rules = sxCollected("cssx"); + return (isSxTruthy(!isSxTruthy(isEmpty(rules))) ? ((function() { + var style = sxOr(domQuery("#sx-cssx-live"), (function() { + var s = domCreateElement("style", NIL); + domSetAttr(s, "id", "sx-cssx-live"); + domSetAttr(s, "data-cssx", ""); + domAppendToHead(s); + return s; +})()); + return domSetProp(style, "textContent", (String(sxOr(domGetProp(style, "textContent"), "")) + String(join("", rules)))); +})(), sxClearCollected("cssx")) : NIL); +})(); }; + // boot-init - var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); }; + var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), flushCssxToDom(), processElements(NIL)); }; // === Transpiled from deps (component dependency analysis) === diff --git a/shared/sx/ref/boot.sx b/shared/sx/ref/boot.sx index aca976a..19ab753 100644 --- a/shared/sx/ref/boot.sx +++ b/shared/sx/ref/boot.sx @@ -87,7 +87,8 @@ ;; Process sx- attributes, hydrate data-sx and islands (process-elements el) (sx-hydrate-elements el) - (sx-hydrate-islands el)))))) + (sx-hydrate-islands el) + (flush-cssx-to-dom)))))) ;; -------------------------------------------------------------------------- @@ -119,6 +120,7 @@ (process-elements el) (sx-hydrate-elements el) (sx-hydrate-islands el) + (flush-cssx-to-dom) (dom-dispatch el "sx:resolved" {:id id}))) (log-warn (str "resolveSuspense: no element for id=" id)))))) @@ -415,6 +417,32 @@ (for-each dispose-island to-dispose)))))))) +;; -------------------------------------------------------------------------- +;; CSSX live flush — inject collected CSS rules into the DOM +;; -------------------------------------------------------------------------- +;; +;; ~cssx/tw collects CSS rules via collect!("cssx" ...) during rendering. +;; On the server, ~cssx/flush emits a batch