Add update/hydrate methods and browser auto-init to sexp.js

Adds Sexp.update() for re-rendering data-sexp elements with new data,
Sexp.hydrate() for finding and rendering all [data-sexp] elements,
and auto-init on DOMContentLoaded + htmx:afterSwap integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 23:40:14 +00:00
parent 39e013a75e
commit 9f2f0dacaf

View File

@@ -1189,6 +1189,49 @@
}
},
/**
* Bind client-side sexp rendering to elements with data-sexp-* attrs.
*
* Pattern:
* <div data-sexp="(~card :title title)" data-sexp-env='{"title":"Hi"}'>
* <!-- server-rendered HTML (hydration target) -->
* </div>
*
* Call Sexp.update(el, {title: "New"}) to re-render with new data.
*/
update: function (target, newEnv) {
var el = typeof target === "string" ? document.querySelector(target) : target;
if (!el) return;
var source = el.getAttribute("data-sexp");
if (!source) return;
var baseEnv = {};
var envAttr = el.getAttribute("data-sexp-env");
if (envAttr) {
try { baseEnv = JSON.parse(envAttr); } catch (e) { /* ignore */ }
}
var env = merge({}, _componentEnv, baseEnv, newEnv || {});
var node = renderDOM(parse(source), env);
el.textContent = "";
el.appendChild(node);
if (newEnv) {
merge(baseEnv, newEnv);
el.setAttribute("data-sexp-env", JSON.stringify(baseEnv));
}
},
/**
* Find all [data-sexp] elements within root and render them.
* Useful after HTMX swaps bring in new sexp-enabled elements.
*/
hydrate: function (root) {
var els = (root || document).querySelectorAll("[data-sexp]");
for (var i = 0; i < els.length; i++) {
if (els[i]._sexpHydrated) continue;
els[i]._sexpHydrated = true;
Sexp.update(els[i]);
}
},
// For testing
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
_eval: sexpEval,
@@ -1203,17 +1246,20 @@
// =========================================================================
if (typeof document !== "undefined") {
// Process sexp scripts on DOMContentLoaded
function init() { Sexp.processScripts(); }
var init = function () {
Sexp.processScripts();
Sexp.hydrate();
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
// Re-process after HTMX swaps (new sexp scripts may have arrived)
// Re-process after HTMX swaps
document.addEventListener("htmx:afterSwap", function (e) {
Sexp.processScripts(e.detail.target);
Sexp.hydrate(e.detail.target);
});
}