Add engine orchestration to SX spec (fetch, triggers, swap, SSE, history, init)

29 orchestration functions written in SX + adapter style: request pipeline
(execute-request, do-fetch, handle-fetch-success), trigger binding (poll,
intersect, load, revealed, event), post-swap processing, OOB swaps, boost,
SSE, inline handlers, preload, history/popstate, and engine-init. Platform
JS implementations in bootstrap_js.py for all browser-specific operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 12:54:39 +00:00
parent 157a32b426
commit d4b23aae4c
4 changed files with 3197 additions and 21 deletions

View File

@@ -1252,6 +1252,354 @@
// parse-sse-swap
var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); };
// _preload-cache
var _preloadCache = {};
// _css-hash
var _cssHash = "";
// dispatch-trigger-events
var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() {
var parsed = tryParseJson(headerVal);
return (isSxTruthy((isSxTruthy(parsed) && isDict(parsed))) ? forEach(function(key) { return domDispatch(el, key, dictGet(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() {
var n = trim(name);
return (isSxTruthy(!(n == "")) ? domDispatch(el, n, {}) : NIL);
})(); }, split(headerVal, ",")));
})() : NIL); };
// init-css-tracking
var initCssTracking = function() { return (function() {
var meta = domQuery("meta[name=\"sx-css-classes\"]");
return (isSxTruthy(meta) ? (function() {
var content = domGetAttr(meta, "content");
return (isSxTruthy(content) ? (_cssHash = content) : NIL);
})() : NIL);
})(); };
// execute-request
var executeRequest = function(el, verbInfo, extraParams) { return (function() {
var currentVerb = getVerbInfo(el);
var verb = (isSxTruthy(currentVerb) ? currentVerb : verbInfo);
var method = get(verb, "method");
var url = get(verb, "url");
if (isSxTruthy(!domHasClass(el, "sx-error"))) {
domRemoveAttr(el, "data-sx-retry-ms");
}
return (isSxTruthy((function() {
var media = domGetAttr(el, "sx-media");
return (isSxTruthy(media) && !browserMediaMatches(media));
})()) ? promiseResolve(NIL) : (isSxTruthy((function() {
var msg = domGetAttr(el, "sx-confirm");
return (isSxTruthy(msg) && !browserConfirm(msg));
})()) ? promiseResolve(NIL) : (function() {
var promptMsg = domGetAttr(el, "sx-prompt");
var params = extraParams;
return (isSxTruthy(promptMsg) ? (function() {
var promptVal = browserPrompt(promptMsg);
return (isSxTruthy(isNil(promptVal)) ? promiseResolve(NIL) : ((params = sxOr(params, {})), dictSet(params, "promptValue", promptVal), doFetch(el, verb, method, url, params)));
})() : doFetch(el, verb, method, url, params));
})()));
})(); };
// do-fetch
var doFetch = function(el, verb, method, url, extraParams) { return (function() {
var syncAttr = domGetAttr(el, "sx-sync");
if (isSxTruthy((isSxTruthy(syncAttr) && contains(syncAttr, "replace")))) {
abortPrevious(el);
}
return (function() {
var ctrl = newAbortController();
trackController(el, ctrl);
return (function() {
var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
if (isSxTruthy((isSxTruthy(extraParams) && dictHas(extraParams, "promptValue")))) {
headers["SX-Prompt"] = get(extraParams, "promptValue");
}
if (isSxTruthy((isSxTruthy(!(method == "GET")) && browserSameOrigin(url)))) {
(function() {
var csrf = csrfToken();
return (isSxTruthy(csrf) ? dictSet(headers, "X-CSRFToken", csrf) : NIL);
})();
}
return (function() {
var bodyInfo = buildRequestBody(el, method, url);
return (function() {
var body = get(bodyInfo, "body");
var finalUrl = get(bodyInfo, "url");
var ct = get(bodyInfo, "content-type");
if (isSxTruthy(ct)) {
headers["Content-Type"] = ct;
}
return (isSxTruthy(!domDispatch(el, "sx:beforeRequest", {["method"]: method, ["url"]: finalUrl})) ? promiseResolve(NIL) : (domAddClass(el, "sx-request"), domSetAttr(el, "aria-busy", "true"), (function() {
var indicator = showIndicator(el);
var disabledElts = disableElements(el);
var preloaded = (isSxTruthy((method == "GET")) ? preloadCacheGet(_preloadCache, finalUrl) : NIL);
return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["preloaded"]: preloaded, ["cross-origin"]: isCrossOrigin(finalUrl)}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), (isSxTruthy(!respOk) ? (domDispatch(el, "sx:responseError", {["status"]: status}), handleRetry(el, verb, extraParams)) : (domDispatch(el, "sx:afterRequest", {}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), (isSxTruthy(!isAbortError(err)) ? (domDispatch(el, "sx:sendError", {["error"]: err}), handleRetry(el, verb, extraParams)) : NIL)); });
})()));
})();
})();
})();
})();
})(); };
// handle-fetch-success
var handleFetchSuccess = function(el, url, verb, extraParams, getHeader, text) { return (function() {
var headers = processResponseHeaders(getHeader);
return (isSxTruthy(get(headers, "redirect")) ? browserNavigate(get(headers, "redirect")) : (isSxTruthy((get(headers, "refresh") == "true")) ? browserReload() : (dispatchTriggerEvents(el, get(headers, "trigger")), (function() {
var rawSwap = sxOr(domGetAttr(el, "sx-swap"), DEFAULT_SWAP);
var target = resolveTarget(el);
var selectSel = domGetAttr(el, "sx-select");
if (isSxTruthy(get(headers, "retarget"))) {
target = sxOr(domQuery(get(headers, "retarget")), target);
}
if (isSxTruthy(get(headers, "reswap"))) {
rawSwap = get(headers, "reswap");
}
return (function() {
var swap = parseSwapSpec(rawSwap, false);
var ct = sxOr(get(headers, "content-type"), "");
(isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, target, swap, selectSel, text) : handleHtmlResponse(el, target, swap, selectSel, text));
if (isSxTruthy(get(headers, "location"))) {
fetchLocation(get(headers, "location"));
}
handleHistory(el, url, headers);
domDispatch(el, "sx:afterSwap", {["target"]: target});
dispatchTriggerEvents(el, get(headers, "trigger-swap"));
return requestAnimationFrame_(function() { return (domDispatch(el, "sx:afterSettle", {["target"]: target}), dispatchTriggerEvents(el, get(headers, "trigger-settle"))); });
})();
})())));
})(); };
// handle-sx-response
var handleSxResponse = function(el, target, swap, selectSel, text) { return (function() {
var cleaned = stripComponentScripts(text);
var cleaned2 = extractResponseCss(cleaned);
return (function() {
var source = trim(cleaned2);
return (isSxTruthy((isSxTruthy(source) && !(source == ""))) ? (function() {
var dom = sxRender(source);
var container = domCreateElement("div", NIL);
domAppend(container, dom);
processOobSwaps(container, function(t, oob, s) { return swapDomNodes(t, oob, s); });
return (function() {
var selected = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container));
return (isSxTruthy((isSxTruthy(!(get(swap, "style") == "none")) && target)) ? withTransition(get(swap, "transition"), function() { return (swapDomNodes(target, selected, get(swap, "style")), hoistHeadElements(target)); }) : NIL);
})();
})() : NIL);
})();
})(); };
// handle-html-response
var handleHtmlResponse = function(el, target, swap, selectSel, text) { return (function() {
var doc = domParseHtmlDocument(text);
sxProcessScripts(doc);
processOobSwaps(doc, function(t, oob, s) { return swapHtmlString(t, domOuterHtml(oob), s); });
return (function() {
var content = (isSxTruthy(selectSel) ? selectHtmlFromDoc(doc, selectSel) : sxOr(domBodyInnerHtml(doc), text));
return (isSxTruthy((isSxTruthy(!(get(swap, "style") == "none")) && target)) ? withTransition(get(swap, "transition"), function() { return (swapHtmlString(target, content, get(swap, "style")), hoistHeadElements(target)); }) : NIL);
})();
})(); };
// handle-retry
var handleRetry = function(el, verbInfo, extraParams) { return (function() {
var retryAttr = domGetAttr(el, "sx-retry");
return (isSxTruthy(retryAttr) ? (function() {
var spec = parseRetrySpec(retryAttr);
var currentMs = sxOr(parseInt_(domGetAttr(el, "data-sx-retry-ms"), 0), get(spec, "start-ms"));
domAddClass(el, "sx-error");
domRemoveClass(el, "sx-loading");
return setTimeout_(function() { return (domRemoveClass(el, "sx-error"), domAddClass(el, "sx-loading"), domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(currentMs, get(spec, "cap-ms"))))), executeRequest(el, verbInfo, extraParams)); }, currentMs);
})() : NIL);
})(); };
// bind-triggers
var bindTriggers = function(el, verbInfo) { return (function() {
var triggerSpec = domGetAttr(el, "sx-trigger");
var triggers = (isSxTruthy(triggerSpec) ? parseTriggerSpec(triggerSpec) : defaultTrigger(domTagName(el)));
return forEach(function(trig) { return (function() {
var kind = classifyTrigger(trig);
return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, verbInfo, NIL); }, sxOr(get(get(trig, "modifiers"), "interval"), 1000)) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, verbInfo, NIL); }, get(get(trig, "modifiers"), "once"), get(get(trig, "modifiers"), "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, 0) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, verbInfo, NIL); }, true, NIL) : bindEvent(el, verbInfo, trig)))));
})(); }, triggers);
})(); };
// bind-event
var bindEvent = function(el, verbInfo, trig) { return (function() {
var eventName = get(trig, "event");
var mods = get(trig, "modifiers");
var listenTarget = (isSxTruthy(get(mods, "from")) ? sxOr(domQuery(get(mods, "from")), el) : el);
var timer = NIL;
var lastVal = NIL;
return domAddListener(listenTarget, eventName, function(e) { return ((isSxTruthy((eventName == "submit")) ? preventDefault_(e) : NIL), (isSxTruthy((isSxTruthy((eventName == "click")) && (domTagName(el) == "A"))) ? preventDefault_(e) : NIL), (isSxTruthy(!validateForRequest(el)) ? domDispatch(el, "sx:validationFailed", {}) : (isSxTruthy((isSxTruthy(get(mods, "changed")) && isSxTruthy(!isNil(elementValue(el))) && (elementValue(el) == lastVal))) ? NIL : ((isSxTruthy(get(mods, "changed")) ? (lastVal = elementValue(el)) : NIL), (function() {
var optState = applyOptimistic(el);
var execFn = function() { return (function() {
var p = executeRequest(el, verbInfo, NIL);
return (isSxTruthy((isSxTruthy(optState) && p)) ? promiseCatch(p, function(_) { return revertOptimistic(optState); }) : NIL);
})(); };
return (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(execFn, get(mods, "delay")))) : execFn());
})())))); }, {["once"]: get(mods, "once")});
})(); };
// post-swap
var postSwap = function(root) { return (activateScripts(root), sxProcessScripts(root), sxHydrate(root), processElements(root)); };
// activate-scripts
var activateScripts = function(root) { return (function() {
var dead = domQueryAll(root, "script:not([type]), script[type='text/javascript']");
return forEach(function(d) { return (function() {
var live = createScriptClone(d);
return domReplaceChild(domParent(d), live, d);
})(); }, dead);
})(); };
// process-oob-swaps
var processOobSwaps = function(container, swapFn) { return forEach(function(attr) { return (function() {
var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]")));
return forEach(function(oob) { return (function() {
var swapType = sxOr(domGetAttr(oob, attr), "outerHTML");
var targetId = domId(oob);
domRemoveAttr(oob, attr);
if (isSxTruthy(domParent(oob))) {
domRemoveChild(domParent(oob), oob);
}
return (isSxTruthy(targetId) ? (function() {
var target = domQueryById(targetId);
return (isSxTruthy(target) ? swapFn(target, oob, swapType) : NIL);
})() : NIL);
})(); }, oobEls);
})(); }, ["sx-swap-oob", "hx-swap-oob"]); };
// hoist-head-elements
var hoistHeadElements = function(root) { return (function() {
var styles = domQueryAll(root, "style[data-sx-css]");
var links = domQueryAll(root, "link[rel='stylesheet']");
{ var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var el = _c[_i]; if (isSxTruthy(domParent(el))) {
domRemoveChild(domParent(el), el);
} } }
return forEach(function(el) { return (isSxTruthy(domParent(el)) ? domRemoveChild(domParent(el), el) : NIL); }, links);
})(); };
// process-boosted
var processBoosted = function(root) { return (function() {
var containers = domQueryAll(root, "[sx-boost]");
if (isSxTruthy(domMatches(root, "[sx-boost]"))) {
boostDescendants(root);
}
return forEach(boostDescendants, containers);
})(); };
// boost-descendants
var boostDescendants = function(container) { return ((function() {
var links = domQueryAll(container, "a[href]");
return forEach(function(link) { return (isSxTruthy((isSxTruthy(!isProcessed(link, "boost")) && shouldBoostLink(link))) ? (markProcessed(link, "boost"), bindBoostLink(link, domGetAttr(link, "href")), (isSxTruthy(!domHasAttr(link, "sx-target")) ? domSetAttr(link, "sx-target", "#main-panel") : NIL), (isSxTruthy(!domHasAttr(link, "sx-swap")) ? domSetAttr(link, "sx-swap", "innerHTML") : NIL), (isSxTruthy(!domHasAttr(link, "sx-select")) ? domSetAttr(link, "sx-select", "#main-panel") : NIL)) : NIL); }, links);
})(), (function() {
var forms = domQueryAll(container, "form");
return forEach(function(form) { return (isSxTruthy((isSxTruthy(!isProcessed(form, "boost")) && shouldBoostForm(form))) ? (markProcessed(form, "boost"), bindBoostForm(form, sxOr(upper(domGetAttr(form, "method")), "GET"), sxOr(domGetAttr(form, "action"), browserLocationHref())), (isSxTruthy(!domHasAttr(form, "sx-target")) ? domSetAttr(form, "sx-target", "#main-panel") : NIL), (isSxTruthy(!domHasAttr(form, "sx-swap")) ? domSetAttr(form, "sx-swap", "innerHTML") : NIL)) : NIL); }, forms);
})()); };
// process-sse
var processSse = function(root) { return (function() {
var sseEls = domQueryAll(root, "[sx-sse]");
if (isSxTruthy(domMatches(root, "[sx-sse]"))) {
bindSse(root);
}
return forEach(bindSse, sseEls);
})(); };
// bind-sse
var bindSse = function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), (function() {
var url = domGetAttr(el, "sx-sse");
return (isSxTruthy(url) ? (function() {
var source = eventSourceConnect(url, el);
return (function() {
var swapEls = domQueryAll(el, "[sx-sse-swap]");
if (isSxTruthy(domHasAttr(el, "sx-sse-swap"))) {
bindSseSwap(el, source);
}
return forEach(function(child) { return bindSseSwap(child, source); }, swapEls);
})();
})() : NIL);
})()) : NIL); };
// bind-sse-swap
var bindSseSwap = function(el, source) { return (function() {
var eventName = parseSseSwap(el);
return eventSourceListen(source, eventName, function(data) { return (function() {
var target = sxOr(resolveTarget(el), el);
var swapStyle = sxOr(domGetAttr(el, "sx-swap"), "innerHTML");
(isSxTruthy(startsWith(trim(data), "(")) ? (function() {
var dom = sxRender(data);
return swapDomNodes(target, dom, swapStyle);
})() : swapHtmlString(target, data, swapStyle));
postSwap(target);
return domDispatch(el, "sx:sseMessage", {["data"]: data, ["event"]: eventName});
})(); });
})(); };
// bind-inline-handlers
var bindInlineHandlers = function(el) { return (isSxTruthy(!isProcessed(el, "on")) ? (markProcessed(el, "on"), (function() {
var attrs = domAttrList(el);
return forEach(function(attr) { return (function() {
var name = first(attr);
var val = nth(attr, 1);
return (isSxTruthy(startsWith(name, "sx-on:")) ? bindInlineHandler(el, slice(name, 6), val) : NIL);
})(); }, attrs);
})()) : NIL); };
// bind-preload-for
var bindPreloadFor = function(el) { return (isSxTruthy(domHasAttr(el, "sx-preload")) ? (function() {
var mode = sxOr(domGetAttr(el, "sx-preload"), "mousedown");
var events = (isSxTruthy((mode == "mouseover")) ? ["mouseenter", "focusin"] : ["mousedown", "focusin"]);
var debounceMs = (isSxTruthy((mode == "mouseover")) ? 100 : 0);
return bindPreload(el, events, debounceMs, function() { return (function() {
var verb = getVerbInfo(el);
return (isSxTruthy(verb) ? (function() {
var url = get(verb, "url");
return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? doPreload(url) : NIL);
})() : NIL);
})(); });
})() : NIL); };
// do-preload
var doPreload = function(url) { return (function() {
var headers = buildRequestHeaders(NIL, loadedComponentNames(), _cssHash);
return fetchPreload(url, headers, _preloadCache);
})(); };
// VERB_SELECTOR
var VERB_SELECTOR = "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]";
// process-elements
var processElements = function(root) { return (function() {
var root = sxOr(root, domBody());
return (isSxTruthy(root) ? ((isSxTruthy(domMatches(root, VERB_SELECTOR)) ? processOne(root) : NIL), (function() {
var elements = domQueryAll(root, VERB_SELECTOR);
return forEach(processOne, elements);
})(), processBoosted(root), processSse(root), (function() {
var onEls = domQueryAll(root, "[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:responseError]");
return forEach(bindInlineHandlers, onEls);
})()) : NIL);
})(); };
// process-one
var processOne = function(el) { return (isSxTruthy(!isProcessed(el, "bound")) ? (isSxTruthy(!sxOr(domHasAttr(el, "sx-disable"), domClosest(el, "[sx-disable]"))) ? (markProcessed(el, "bound"), (function() {
var verbInfo = getVerbInfo(el);
return (isSxTruthy(verbInfo) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL);
})()) : NIL) : NIL); };
// handle-popstate
var handlePopstate = function(scrollY) { return (function() {
var url = browserLocationHref();
var main = domQueryById("main-panel");
return (isSxTruthy(!main) ? browserReload() : (function() {
var headers = buildRequestHeaders(NIL, loadedComponentNames(), _cssHash);
headers["SX-History-Restore"] = "true";
return fetchAndRestore(main, url, headers, scrollY);
})());
})(); };
// engine-init
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
// =========================================================================
// Platform interface — DOM adapter (browser-only)
@@ -1411,6 +1759,8 @@
// Platform interface — Engine (browser-only)
// =========================================================================
// --- Browser/Network ---
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
@@ -1472,6 +1822,531 @@
} catch (e) { return null; }
}
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute("content") : NIL;
}
function isCrossOrigin(url) {
try {
var h = new URL(url, location.href).hostname;
return h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0);
} catch (e) { return false; }
}
// --- Promises ---
function promiseResolve(val) { return Promise.resolve(val); }
function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; }
// --- Abort controllers ---
var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
function abortPrevious(el) {
if (_controllers) {
var prev = _controllers.get(el);
if (prev) prev.abort();
}
}
function trackController(el, ctrl) {
if (_controllers) _controllers.set(el, ctrl);
}
function newAbortController() {
return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} };
}
function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; }
function isAbortError(err) { return err && err.name === "AbortError"; }
// --- Timers ---
function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); }
function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); }
function clearTimeout_(id) { clearTimeout(id); }
function requestAnimationFrame_(fn) {
if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(fn);
else setTimeout(fn, 16);
}
// --- Fetch ---
function fetchRequest(config, successFn, errorFn) {
var opts = { method: config.method, headers: config.headers };
if (config.signal) opts.signal = config.signal;
if (config.body && config.method !== "GET") opts.body = config.body;
if (config["cross-origin"]) opts.credentials = "include";
var p = config.preloaded
? Promise.resolve({
ok: true, status: 200,
headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }),
text: function() { return Promise.resolve(config.preloaded.text); }
})
: fetch(config.url, opts);
return p.then(function(resp) {
return resp.text().then(function(text) {
var getHeader = function(name) {
var v = resp.headers.get(name);
return v === null ? NIL : v;
};
return successFn(resp.ok, resp.status, getHeader, text);
});
}).catch(function(err) {
return errorFn(err);
});
}
function fetchLocation(headerVal) {
if (!_hasDom) return;
var locUrl = headerVal;
try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {}
fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) {
return r.text().then(function(t) {
var main = document.getElementById("main-panel");
if (main) {
main.innerHTML = t;
postSwap(main);
try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {}
}
});
});
}
function fetchAndRestore(main, url, headers, scrollY) {
var opts = { headers: headers };
try {
var h = new URL(url, location.href).hostname;
if (h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) {
opts.credentials = "include";
}
} catch (e) {}
fetch(url, opts).then(function(resp) {
return resp.text().then(function(text) {
text = stripComponentScripts(text);
text = extractResponseCss(text);
text = text.trim();
if (text.charAt(0) === "(") {
try {
var dom = sxRender(text);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(main, newMain || container);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} catch (err) {
console.error("sx-ref popstate error:", err);
location.reload();
}
} else {
var parser = new DOMParser();
var doc = parser.parseFromString(text, "text/html");
var newMain = doc.getElementById("main-panel");
if (newMain) {
morphChildren(main, newMain);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} else {
location.reload();
}
}
});
}).catch(function() { location.reload(); });
}
function fetchPreload(url, headers, cache) {
fetch(url, { headers: headers }).then(function(resp) {
if (!resp.ok) return;
var ct = resp.headers.get("Content-Type") || "";
return resp.text().then(function(text) {
preloadCacheSet(cache, url, text, ct);
});
}).catch(function() { /* ignore */ });
}
// --- Request body building ---
function buildRequestBody(el, method, url) {
if (!_hasDom) return { body: null, url: url, "content-type": NIL };
var body = null;
var ct = NIL;
var finalUrl = url;
var isJson = el.getAttribute("sx-encoding") === "json";
if (method !== "GET") {
var form = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form) {
if (isJson) {
var fd = new FormData(form);
var obj = {};
fd.forEach(function(v, k) {
if (obj[k] !== undefined) {
if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
obj[k].push(v);
} else { obj[k] = v; }
});
body = JSON.stringify(obj);
ct = "application/json";
} else {
body = new URLSearchParams(new FormData(form));
ct = "application/x-www-form-urlencoded";
}
}
}
// sx-params
var paramsSpec = el.getAttribute("sx-params");
if (paramsSpec && body instanceof URLSearchParams) {
if (paramsSpec === "none") {
body = new URLSearchParams();
} else if (paramsSpec.indexOf("not ") === 0) {
paramsSpec.substring(4).split(",").forEach(function(k) { body.delete(k.trim()); });
} else if (paramsSpec !== "*") {
var allowed = paramsSpec.split(",").map(function(s) { return s.trim(); });
var filtered = new URLSearchParams();
allowed.forEach(function(k) {
body.getAll(k).forEach(function(v) { filtered.append(k, v); });
});
body = filtered;
}
}
// sx-include
var includeSel = el.getAttribute("sx-include");
if (includeSel && method !== "GET") {
if (!body) body = new URLSearchParams();
document.querySelectorAll(includeSel).forEach(function(inp) {
if (inp.name) body.append(inp.name, inp.value);
});
}
// sx-vals
var valsAttr = el.getAttribute("sx-vals");
if (valsAttr) {
try {
var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr);
if (method === "GET") {
for (var vk in vals) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]);
} else if (body instanceof URLSearchParams) {
for (var vk2 in vals) body.append(vk2, vals[vk2]);
} else if (!body) {
body = new URLSearchParams();
for (var vk3 in vals) body.append(vk3, vals[vk3]);
ct = "application/x-www-form-urlencoded";
}
} catch (e) {}
}
// GET form data → URL
if (method === "GET") {
var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form2) {
var qs = new URLSearchParams(new FormData(form2)).toString();
if (qs) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + qs;
}
if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) {
finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value);
}
}
return { body: body, url: finalUrl, "content-type": ct };
}
// --- Loading state ---
function showIndicator(el) {
if (!_hasDom) return NIL;
var sel = el.getAttribute("sx-indicator");
var ind = sel ? (document.querySelector(sel) || el.closest(sel)) : null;
if (ind) { ind.classList.add("sx-request"); ind.style.display = ""; }
return ind || NIL;
}
function disableElements(el) {
if (!_hasDom) return [];
var sel = el.getAttribute("sx-disabled-elt");
if (!sel) return [];
var elts = Array.prototype.slice.call(document.querySelectorAll(sel));
elts.forEach(function(e) { e.disabled = true; });
return elts;
}
function clearLoadingState(el, indicator, disabledElts) {
el.classList.remove("sx-request");
el.removeAttribute("aria-busy");
if (indicator && !isNil(indicator)) {
indicator.classList.remove("sx-request");
indicator.style.display = "none";
}
if (disabledElts) {
for (var i = 0; i < disabledElts.length; i++) disabledElts[i].disabled = false;
}
}
// --- DOM extras ---
function domQueryById(id) {
return _hasDom ? document.getElementById(id) : null;
}
function domMatches(el, sel) {
return el && el.matches ? el.matches(sel) : false;
}
function domClosest(el, sel) {
return el && el.closest ? el.closest(sel) : null;
}
function domBody() {
return _hasDom ? document.body : null;
}
function domHasClass(el, cls) {
return el && el.classList ? el.classList.contains(cls) : false;
}
function domAppendToHead(el) {
if (_hasDom && document.head) document.head.appendChild(el);
}
function domParseHtmlDocument(text) {
if (!_hasDom) return null;
return new DOMParser().parseFromString(text, "text/html");
}
function domOuterHtml(el) {
return el ? el.outerHTML : "";
}
function domBodyInnerHtml(doc) {
return doc && doc.body ? doc.body.innerHTML : "";
}
// --- Events ---
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; }
function domAddListener(el, event, fn, opts) {
if (!el || !el.addEventListener) return;
var o = {};
if (opts && !isNil(opts)) {
if (opts.once || opts["once"]) o.once = true;
}
el.addEventListener(event, fn, o);
}
// --- Validation ---
function validateForRequest(el) {
if (!_hasDom) return true;
var attr = el.getAttribute("sx-validate");
if (attr === null) {
var vForm = el.closest("[sx-validate]");
if (vForm) attr = vForm.getAttribute("sx-validate");
}
if (attr === null) return true; // no validation configured
var form = el.tagName === "FORM" ? el : el.closest("form");
if (form && !form.reportValidity()) return false;
if (attr && attr !== "true" && attr !== "") {
var fn = window[attr];
if (typeof fn === "function" && !fn(el)) return false;
}
return true;
}
// --- View Transitions ---
function withTransition(enabled, fn) {
if (enabled && _hasDom && document.startViewTransition) {
document.startViewTransition(fn);
} else {
fn();
}
}
// --- IntersectionObserver ---
function observeIntersection(el, fn, once, delay) {
if (!_hasDom || !("IntersectionObserver" in window)) { fn(); return; }
var fired = false;
var d = isNil(delay) ? 0 : delay;
var obs = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (!entry.isIntersecting) return;
if (once && fired) return;
fired = true;
if (once) obs.unobserve(el);
if (d) setTimeout(fn, d); else fn();
});
});
obs.observe(el);
}
// --- EventSource ---
function eventSourceConnect(url, el) {
var source = new EventSource(url);
source.addEventListener("error", function() { domDispatch(el, "sx:sseError", {}); });
source.addEventListener("open", function() { domDispatch(el, "sx:sseOpen", {}); });
if (typeof MutationObserver !== "undefined") {
var obs = new MutationObserver(function() {
if (!document.body.contains(el)) { source.close(); obs.disconnect(); }
});
obs.observe(document.body, { childList: true, subtree: true });
}
return source;
}
function eventSourceListen(source, event, fn) {
source.addEventListener(event, function(e) { fn(e.data); });
}
// --- Boost bindings ---
function bindBoostLink(el, href) {
el.addEventListener("click", function(e) {
e.preventDefault();
executeRequest(el, { method: "GET", url: href }).then(function() {
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
});
});
}
function bindBoostForm(form, method, action) {
form.addEventListener("submit", function(e) {
e.preventDefault();
executeRequest(form, { method: method, url: action }).then(function() {
try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {}
});
});
}
// --- Inline handlers ---
function bindInlineHandler(el, eventName, body) {
el.addEventListener(eventName, new Function("event", body));
}
// --- Preload binding ---
function bindPreload(el, events, debounceMs, fn) {
var timer = null;
events.forEach(function(evt) {
el.addEventListener(evt, function() {
if (debounceMs) {
clearTimeout(timer);
timer = setTimeout(fn, debounceMs);
} else {
fn();
}
});
});
}
// --- Processing markers ---
var PROCESSED = "_sxBound";
function markProcessed(el, key) { el[PROCESSED + key] = true; }
function isProcessed(el, key) { return !!el[PROCESSED + key]; }
// --- Script cloning ---
function createScriptClone(dead) {
var live = document.createElement("script");
for (var i = 0; i < dead.attributes.length; i++)
live.setAttribute(dead.attributes[i].name, dead.attributes[i].value);
live.textContent = dead.textContent;
return live;
}
// --- SX API references ---
function sxRender(source) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.render) return SxObj.render(source);
throw new Error("No SX renderer available");
}
function sxProcessScripts(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.processScripts) SxObj.processScripts(root || undefined);
}
function sxHydrate(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.hydrate) SxObj.hydrate(root || undefined);
}
function loadedComponentNames() {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (!SxObj) return [];
var env = SxObj.componentEnv || (SxObj.getEnv ? SxObj.getEnv() : {});
return Object.keys(env).filter(function(k) { return k.charAt(0) === "~"; });
}
// --- Response processing ---
function stripComponentScripts(text) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
return text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function extractResponseCss(text) {
if (!_hasDom) return text;
var target = document.getElementById("sx-css");
if (!target) return text;
return text.replace(/<style[^>]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi,
function(_, css) { target.textContent += css; return ""; });
}
function selectFromContainer(container, sel) {
var frag = document.createDocumentFragment();
sel.split(",").forEach(function(s) {
container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); });
});
return frag;
}
function childrenToFragment(container) {
var frag = document.createDocumentFragment();
while (container.firstChild) frag.appendChild(container.firstChild);
return frag;
}
function selectHtmlFromDoc(doc, sel) {
var parts = sel.split(",").map(function(s) { return s.trim(); });
var frags = [];
parts.forEach(function(s) {
doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); });
});
return frags.join("");
}
// --- Parsing ---
function tryParseJson(s) {
if (!s) return NIL;
try { return JSON.parse(s); } catch (e) { return NIL; }
}
// =========================================================================
// Post-transpilation fixups
@@ -1625,9 +2500,30 @@
morphNode: typeof morphNode === "function" ? morphNode : null,
morphChildren: typeof morphChildren === "function" ? morphChildren : null,
swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null,
process: typeof processElements === "function" ? processElements : null,
executeRequest: typeof executeRequest === "function" ? executeRequest : null,
postSwap: typeof postSwap === "function" ? postSwap : null,
init: typeof engineInit === "function" ? engineInit : null,
_version: "ref-2.0 (dom+engine, bootstrap-compiled)"
};
// --- Popstate listener ---
if (typeof window !== "undefined") {
window.addEventListener("popstate", function(e) {
handlePopstate(e && e.state ? e.state.scrollY || 0 : 0);
});
}
// --- Auto-init ---
if (typeof document !== "undefined") {
var _sxRefInit = function() { engineInit(); };
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", _sxRefInit);
} else {
_sxRefInit();
}
}
if (typeof module !== "undefined" && module.exports) module.exports = SxRef;
else global.SxRef = SxRef;

View File

@@ -1400,6 +1400,354 @@
// parse-sse-swap
var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); };
// _preload-cache
var _preloadCache = {};
// _css-hash
var _cssHash = "";
// dispatch-trigger-events
var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() {
var parsed = tryParseJson(headerVal);
return (isSxTruthy((isSxTruthy(parsed) && isDict(parsed))) ? forEach(function(key) { return domDispatch(el, key, dictGet(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() {
var n = trim(name);
return (isSxTruthy(!(n == "")) ? domDispatch(el, n, {}) : NIL);
})(); }, split(headerVal, ",")));
})() : NIL); };
// init-css-tracking
var initCssTracking = function() { return (function() {
var meta = domQuery("meta[name=\"sx-css-classes\"]");
return (isSxTruthy(meta) ? (function() {
var content = domGetAttr(meta, "content");
return (isSxTruthy(content) ? (_cssHash = content) : NIL);
})() : NIL);
})(); };
// execute-request
var executeRequest = function(el, verbInfo, extraParams) { return (function() {
var currentVerb = getVerbInfo(el);
var verb = (isSxTruthy(currentVerb) ? currentVerb : verbInfo);
var method = get(verb, "method");
var url = get(verb, "url");
if (isSxTruthy(!domHasClass(el, "sx-error"))) {
domRemoveAttr(el, "data-sx-retry-ms");
}
return (isSxTruthy((function() {
var media = domGetAttr(el, "sx-media");
return (isSxTruthy(media) && !browserMediaMatches(media));
})()) ? promiseResolve(NIL) : (isSxTruthy((function() {
var msg = domGetAttr(el, "sx-confirm");
return (isSxTruthy(msg) && !browserConfirm(msg));
})()) ? promiseResolve(NIL) : (function() {
var promptMsg = domGetAttr(el, "sx-prompt");
var params = extraParams;
return (isSxTruthy(promptMsg) ? (function() {
var promptVal = browserPrompt(promptMsg);
return (isSxTruthy(isNil(promptVal)) ? promiseResolve(NIL) : ((params = sxOr(params, {})), dictSet(params, "promptValue", promptVal), doFetch(el, verb, method, url, params)));
})() : doFetch(el, verb, method, url, params));
})()));
})(); };
// do-fetch
var doFetch = function(el, verb, method, url, extraParams) { return (function() {
var syncAttr = domGetAttr(el, "sx-sync");
if (isSxTruthy((isSxTruthy(syncAttr) && contains(syncAttr, "replace")))) {
abortPrevious(el);
}
return (function() {
var ctrl = newAbortController();
trackController(el, ctrl);
return (function() {
var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
if (isSxTruthy((isSxTruthy(extraParams) && dictHas(extraParams, "promptValue")))) {
headers["SX-Prompt"] = get(extraParams, "promptValue");
}
if (isSxTruthy((isSxTruthy(!(method == "GET")) && browserSameOrigin(url)))) {
(function() {
var csrf = csrfToken();
return (isSxTruthy(csrf) ? dictSet(headers, "X-CSRFToken", csrf) : NIL);
})();
}
return (function() {
var bodyInfo = buildRequestBody(el, method, url);
return (function() {
var body = get(bodyInfo, "body");
var finalUrl = get(bodyInfo, "url");
var ct = get(bodyInfo, "content-type");
if (isSxTruthy(ct)) {
headers["Content-Type"] = ct;
}
return (isSxTruthy(!domDispatch(el, "sx:beforeRequest", {["method"]: method, ["url"]: finalUrl})) ? promiseResolve(NIL) : (domAddClass(el, "sx-request"), domSetAttr(el, "aria-busy", "true"), (function() {
var indicator = showIndicator(el);
var disabledElts = disableElements(el);
var preloaded = (isSxTruthy((method == "GET")) ? preloadCacheGet(_preloadCache, finalUrl) : NIL);
return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["preloaded"]: preloaded, ["cross-origin"]: isCrossOrigin(finalUrl)}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), (isSxTruthy(!respOk) ? (domDispatch(el, "sx:responseError", {["status"]: status}), handleRetry(el, verb, extraParams)) : (domDispatch(el, "sx:afterRequest", {}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), (isSxTruthy(!isAbortError(err)) ? (domDispatch(el, "sx:sendError", {["error"]: err}), handleRetry(el, verb, extraParams)) : NIL)); });
})()));
})();
})();
})();
})();
})(); };
// handle-fetch-success
var handleFetchSuccess = function(el, url, verb, extraParams, getHeader, text) { return (function() {
var headers = processResponseHeaders(getHeader);
return (isSxTruthy(get(headers, "redirect")) ? browserNavigate(get(headers, "redirect")) : (isSxTruthy((get(headers, "refresh") == "true")) ? browserReload() : (dispatchTriggerEvents(el, get(headers, "trigger")), (function() {
var rawSwap = sxOr(domGetAttr(el, "sx-swap"), DEFAULT_SWAP);
var target = resolveTarget(el);
var selectSel = domGetAttr(el, "sx-select");
if (isSxTruthy(get(headers, "retarget"))) {
target = sxOr(domQuery(get(headers, "retarget")), target);
}
if (isSxTruthy(get(headers, "reswap"))) {
rawSwap = get(headers, "reswap");
}
return (function() {
var swap = parseSwapSpec(rawSwap, false);
var ct = sxOr(get(headers, "content-type"), "");
(isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, target, swap, selectSel, text) : handleHtmlResponse(el, target, swap, selectSel, text));
if (isSxTruthy(get(headers, "location"))) {
fetchLocation(get(headers, "location"));
}
handleHistory(el, url, headers);
domDispatch(el, "sx:afterSwap", {["target"]: target});
dispatchTriggerEvents(el, get(headers, "trigger-swap"));
return requestAnimationFrame_(function() { return (domDispatch(el, "sx:afterSettle", {["target"]: target}), dispatchTriggerEvents(el, get(headers, "trigger-settle"))); });
})();
})())));
})(); };
// handle-sx-response
var handleSxResponse = function(el, target, swap, selectSel, text) { return (function() {
var cleaned = stripComponentScripts(text);
var cleaned2 = extractResponseCss(cleaned);
return (function() {
var source = trim(cleaned2);
return (isSxTruthy((isSxTruthy(source) && !(source == ""))) ? (function() {
var dom = sxRender(source);
var container = domCreateElement("div", NIL);
domAppend(container, dom);
processOobSwaps(container, function(t, oob, s) { return swapDomNodes(t, oob, s); });
return (function() {
var selected = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container));
return (isSxTruthy((isSxTruthy(!(get(swap, "style") == "none")) && target)) ? withTransition(get(swap, "transition"), function() { return (swapDomNodes(target, selected, get(swap, "style")), hoistHeadElements(target)); }) : NIL);
})();
})() : NIL);
})();
})(); };
// handle-html-response
var handleHtmlResponse = function(el, target, swap, selectSel, text) { return (function() {
var doc = domParseHtmlDocument(text);
sxProcessScripts(doc);
processOobSwaps(doc, function(t, oob, s) { return swapHtmlString(t, domOuterHtml(oob), s); });
return (function() {
var content = (isSxTruthy(selectSel) ? selectHtmlFromDoc(doc, selectSel) : sxOr(domBodyInnerHtml(doc), text));
return (isSxTruthy((isSxTruthy(!(get(swap, "style") == "none")) && target)) ? withTransition(get(swap, "transition"), function() { return (swapHtmlString(target, content, get(swap, "style")), hoistHeadElements(target)); }) : NIL);
})();
})(); };
// handle-retry
var handleRetry = function(el, verbInfo, extraParams) { return (function() {
var retryAttr = domGetAttr(el, "sx-retry");
return (isSxTruthy(retryAttr) ? (function() {
var spec = parseRetrySpec(retryAttr);
var currentMs = sxOr(parseInt_(domGetAttr(el, "data-sx-retry-ms"), 0), get(spec, "start-ms"));
domAddClass(el, "sx-error");
domRemoveClass(el, "sx-loading");
return setTimeout_(function() { return (domRemoveClass(el, "sx-error"), domAddClass(el, "sx-loading"), domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(currentMs, get(spec, "cap-ms"))))), executeRequest(el, verbInfo, extraParams)); }, currentMs);
})() : NIL);
})(); };
// bind-triggers
var bindTriggers = function(el, verbInfo) { return (function() {
var triggerSpec = domGetAttr(el, "sx-trigger");
var triggers = (isSxTruthy(triggerSpec) ? parseTriggerSpec(triggerSpec) : defaultTrigger(domTagName(el)));
return forEach(function(trig) { return (function() {
var kind = classifyTrigger(trig);
return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, verbInfo, NIL); }, sxOr(get(get(trig, "modifiers"), "interval"), 1000)) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, verbInfo, NIL); }, get(get(trig, "modifiers"), "once"), get(get(trig, "modifiers"), "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, 0) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, verbInfo, NIL); }, true, NIL) : bindEvent(el, verbInfo, trig)))));
})(); }, triggers);
})(); };
// bind-event
var bindEvent = function(el, verbInfo, trig) { return (function() {
var eventName = get(trig, "event");
var mods = get(trig, "modifiers");
var listenTarget = (isSxTruthy(get(mods, "from")) ? sxOr(domQuery(get(mods, "from")), el) : el);
var timer = NIL;
var lastVal = NIL;
return domAddListener(listenTarget, eventName, function(e) { return ((isSxTruthy((eventName == "submit")) ? preventDefault_(e) : NIL), (isSxTruthy((isSxTruthy((eventName == "click")) && (domTagName(el) == "A"))) ? preventDefault_(e) : NIL), (isSxTruthy(!validateForRequest(el)) ? domDispatch(el, "sx:validationFailed", {}) : (isSxTruthy((isSxTruthy(get(mods, "changed")) && isSxTruthy(!isNil(elementValue(el))) && (elementValue(el) == lastVal))) ? NIL : ((isSxTruthy(get(mods, "changed")) ? (lastVal = elementValue(el)) : NIL), (function() {
var optState = applyOptimistic(el);
var execFn = function() { return (function() {
var p = executeRequest(el, verbInfo, NIL);
return (isSxTruthy((isSxTruthy(optState) && p)) ? promiseCatch(p, function(_) { return revertOptimistic(optState); }) : NIL);
})(); };
return (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(execFn, get(mods, "delay")))) : execFn());
})())))); }, {["once"]: get(mods, "once")});
})(); };
// post-swap
var postSwap = function(root) { return (activateScripts(root), sxProcessScripts(root), sxHydrate(root), processElements(root)); };
// activate-scripts
var activateScripts = function(root) { return (function() {
var dead = domQueryAll(root, "script:not([type]), script[type='text/javascript']");
return forEach(function(d) { return (function() {
var live = createScriptClone(d);
return domReplaceChild(domParent(d), live, d);
})(); }, dead);
})(); };
// process-oob-swaps
var processOobSwaps = function(container, swapFn) { return forEach(function(attr) { return (function() {
var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]")));
return forEach(function(oob) { return (function() {
var swapType = sxOr(domGetAttr(oob, attr), "outerHTML");
var targetId = domId(oob);
domRemoveAttr(oob, attr);
if (isSxTruthy(domParent(oob))) {
domRemoveChild(domParent(oob), oob);
}
return (isSxTruthy(targetId) ? (function() {
var target = domQueryById(targetId);
return (isSxTruthy(target) ? swapFn(target, oob, swapType) : NIL);
})() : NIL);
})(); }, oobEls);
})(); }, ["sx-swap-oob", "hx-swap-oob"]); };
// hoist-head-elements
var hoistHeadElements = function(root) { return (function() {
var styles = domQueryAll(root, "style[data-sx-css]");
var links = domQueryAll(root, "link[rel='stylesheet']");
{ var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var el = _c[_i]; if (isSxTruthy(domParent(el))) {
domRemoveChild(domParent(el), el);
} } }
return forEach(function(el) { return (isSxTruthy(domParent(el)) ? domRemoveChild(domParent(el), el) : NIL); }, links);
})(); };
// process-boosted
var processBoosted = function(root) { return (function() {
var containers = domQueryAll(root, "[sx-boost]");
if (isSxTruthy(domMatches(root, "[sx-boost]"))) {
boostDescendants(root);
}
return forEach(boostDescendants, containers);
})(); };
// boost-descendants
var boostDescendants = function(container) { return ((function() {
var links = domQueryAll(container, "a[href]");
return forEach(function(link) { return (isSxTruthy((isSxTruthy(!isProcessed(link, "boost")) && shouldBoostLink(link))) ? (markProcessed(link, "boost"), bindBoostLink(link, domGetAttr(link, "href")), (isSxTruthy(!domHasAttr(link, "sx-target")) ? domSetAttr(link, "sx-target", "#main-panel") : NIL), (isSxTruthy(!domHasAttr(link, "sx-swap")) ? domSetAttr(link, "sx-swap", "innerHTML") : NIL), (isSxTruthy(!domHasAttr(link, "sx-select")) ? domSetAttr(link, "sx-select", "#main-panel") : NIL)) : NIL); }, links);
})(), (function() {
var forms = domQueryAll(container, "form");
return forEach(function(form) { return (isSxTruthy((isSxTruthy(!isProcessed(form, "boost")) && shouldBoostForm(form))) ? (markProcessed(form, "boost"), bindBoostForm(form, sxOr(upper(domGetAttr(form, "method")), "GET"), sxOr(domGetAttr(form, "action"), browserLocationHref())), (isSxTruthy(!domHasAttr(form, "sx-target")) ? domSetAttr(form, "sx-target", "#main-panel") : NIL), (isSxTruthy(!domHasAttr(form, "sx-swap")) ? domSetAttr(form, "sx-swap", "innerHTML") : NIL)) : NIL); }, forms);
})()); };
// process-sse
var processSse = function(root) { return (function() {
var sseEls = domQueryAll(root, "[sx-sse]");
if (isSxTruthy(domMatches(root, "[sx-sse]"))) {
bindSse(root);
}
return forEach(bindSse, sseEls);
})(); };
// bind-sse
var bindSse = function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), (function() {
var url = domGetAttr(el, "sx-sse");
return (isSxTruthy(url) ? (function() {
var source = eventSourceConnect(url, el);
return (function() {
var swapEls = domQueryAll(el, "[sx-sse-swap]");
if (isSxTruthy(domHasAttr(el, "sx-sse-swap"))) {
bindSseSwap(el, source);
}
return forEach(function(child) { return bindSseSwap(child, source); }, swapEls);
})();
})() : NIL);
})()) : NIL); };
// bind-sse-swap
var bindSseSwap = function(el, source) { return (function() {
var eventName = parseSseSwap(el);
return eventSourceListen(source, eventName, function(data) { return (function() {
var target = sxOr(resolveTarget(el), el);
var swapStyle = sxOr(domGetAttr(el, "sx-swap"), "innerHTML");
(isSxTruthy(startsWith(trim(data), "(")) ? (function() {
var dom = sxRender(data);
return swapDomNodes(target, dom, swapStyle);
})() : swapHtmlString(target, data, swapStyle));
postSwap(target);
return domDispatch(el, "sx:sseMessage", {["data"]: data, ["event"]: eventName});
})(); });
})(); };
// bind-inline-handlers
var bindInlineHandlers = function(el) { return (isSxTruthy(!isProcessed(el, "on")) ? (markProcessed(el, "on"), (function() {
var attrs = domAttrList(el);
return forEach(function(attr) { return (function() {
var name = first(attr);
var val = nth(attr, 1);
return (isSxTruthy(startsWith(name, "sx-on:")) ? bindInlineHandler(el, slice(name, 6), val) : NIL);
})(); }, attrs);
})()) : NIL); };
// bind-preload-for
var bindPreloadFor = function(el) { return (isSxTruthy(domHasAttr(el, "sx-preload")) ? (function() {
var mode = sxOr(domGetAttr(el, "sx-preload"), "mousedown");
var events = (isSxTruthy((mode == "mouseover")) ? ["mouseenter", "focusin"] : ["mousedown", "focusin"]);
var debounceMs = (isSxTruthy((mode == "mouseover")) ? 100 : 0);
return bindPreload(el, events, debounceMs, function() { return (function() {
var verb = getVerbInfo(el);
return (isSxTruthy(verb) ? (function() {
var url = get(verb, "url");
return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? doPreload(url) : NIL);
})() : NIL);
})(); });
})() : NIL); };
// do-preload
var doPreload = function(url) { return (function() {
var headers = buildRequestHeaders(NIL, loadedComponentNames(), _cssHash);
return fetchPreload(url, headers, _preloadCache);
})(); };
// VERB_SELECTOR
var VERB_SELECTOR = "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]";
// process-elements
var processElements = function(root) { return (function() {
var root = sxOr(root, domBody());
return (isSxTruthy(root) ? ((isSxTruthy(domMatches(root, VERB_SELECTOR)) ? processOne(root) : NIL), (function() {
var elements = domQueryAll(root, VERB_SELECTOR);
return forEach(processOne, elements);
})(), processBoosted(root), processSse(root), (function() {
var onEls = domQueryAll(root, "[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:responseError]");
return forEach(bindInlineHandlers, onEls);
})()) : NIL);
})(); };
// process-one
var processOne = function(el) { return (isSxTruthy(!isProcessed(el, "bound")) ? (isSxTruthy(!sxOr(domHasAttr(el, "sx-disable"), domClosest(el, "[sx-disable]"))) ? (markProcessed(el, "bound"), (function() {
var verbInfo = getVerbInfo(el);
return (isSxTruthy(verbInfo) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL);
})()) : NIL) : NIL); };
// handle-popstate
var handlePopstate = function(scrollY) { return (function() {
var url = browserLocationHref();
var main = domQueryById("main-panel");
return (isSxTruthy(!main) ? browserReload() : (function() {
var headers = buildRequestHeaders(NIL, loadedComponentNames(), _cssHash);
headers["SX-History-Restore"] = "true";
return fetchAndRestore(main, url, headers, scrollY);
})());
})(); };
// engine-init
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
// =========================================================================
// Platform interface — DOM adapter (browser-only)
@@ -1559,6 +1907,8 @@
// Platform interface — Engine (browser-only)
// =========================================================================
// --- Browser/Network ---
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
@@ -1620,6 +1970,531 @@
} catch (e) { return null; }
}
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute("content") : NIL;
}
function isCrossOrigin(url) {
try {
var h = new URL(url, location.href).hostname;
return h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0);
} catch (e) { return false; }
}
// --- Promises ---
function promiseResolve(val) { return Promise.resolve(val); }
function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; }
// --- Abort controllers ---
var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
function abortPrevious(el) {
if (_controllers) {
var prev = _controllers.get(el);
if (prev) prev.abort();
}
}
function trackController(el, ctrl) {
if (_controllers) _controllers.set(el, ctrl);
}
function newAbortController() {
return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} };
}
function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; }
function isAbortError(err) { return err && err.name === "AbortError"; }
// --- Timers ---
function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); }
function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); }
function clearTimeout_(id) { clearTimeout(id); }
function requestAnimationFrame_(fn) {
if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(fn);
else setTimeout(fn, 16);
}
// --- Fetch ---
function fetchRequest(config, successFn, errorFn) {
var opts = { method: config.method, headers: config.headers };
if (config.signal) opts.signal = config.signal;
if (config.body && config.method !== "GET") opts.body = config.body;
if (config["cross-origin"]) opts.credentials = "include";
var p = config.preloaded
? Promise.resolve({
ok: true, status: 200,
headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }),
text: function() { return Promise.resolve(config.preloaded.text); }
})
: fetch(config.url, opts);
return p.then(function(resp) {
return resp.text().then(function(text) {
var getHeader = function(name) {
var v = resp.headers.get(name);
return v === null ? NIL : v;
};
return successFn(resp.ok, resp.status, getHeader, text);
});
}).catch(function(err) {
return errorFn(err);
});
}
function fetchLocation(headerVal) {
if (!_hasDom) return;
var locUrl = headerVal;
try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {}
fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) {
return r.text().then(function(t) {
var main = document.getElementById("main-panel");
if (main) {
main.innerHTML = t;
postSwap(main);
try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {}
}
});
});
}
function fetchAndRestore(main, url, headers, scrollY) {
var opts = { headers: headers };
try {
var h = new URL(url, location.href).hostname;
if (h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) {
opts.credentials = "include";
}
} catch (e) {}
fetch(url, opts).then(function(resp) {
return resp.text().then(function(text) {
text = stripComponentScripts(text);
text = extractResponseCss(text);
text = text.trim();
if (text.charAt(0) === "(") {
try {
var dom = sxRender(text);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(main, newMain || container);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} catch (err) {
console.error("sx-ref popstate error:", err);
location.reload();
}
} else {
var parser = new DOMParser();
var doc = parser.parseFromString(text, "text/html");
var newMain = doc.getElementById("main-panel");
if (newMain) {
morphChildren(main, newMain);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} else {
location.reload();
}
}
});
}).catch(function() { location.reload(); });
}
function fetchPreload(url, headers, cache) {
fetch(url, { headers: headers }).then(function(resp) {
if (!resp.ok) return;
var ct = resp.headers.get("Content-Type") || "";
return resp.text().then(function(text) {
preloadCacheSet(cache, url, text, ct);
});
}).catch(function() { /* ignore */ });
}
// --- Request body building ---
function buildRequestBody(el, method, url) {
if (!_hasDom) return { body: null, url: url, "content-type": NIL };
var body = null;
var ct = NIL;
var finalUrl = url;
var isJson = el.getAttribute("sx-encoding") === "json";
if (method !== "GET") {
var form = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form) {
if (isJson) {
var fd = new FormData(form);
var obj = {};
fd.forEach(function(v, k) {
if (obj[k] !== undefined) {
if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
obj[k].push(v);
} else { obj[k] = v; }
});
body = JSON.stringify(obj);
ct = "application/json";
} else {
body = new URLSearchParams(new FormData(form));
ct = "application/x-www-form-urlencoded";
}
}
}
// sx-params
var paramsSpec = el.getAttribute("sx-params");
if (paramsSpec && body instanceof URLSearchParams) {
if (paramsSpec === "none") {
body = new URLSearchParams();
} else if (paramsSpec.indexOf("not ") === 0) {
paramsSpec.substring(4).split(",").forEach(function(k) { body.delete(k.trim()); });
} else if (paramsSpec !== "*") {
var allowed = paramsSpec.split(",").map(function(s) { return s.trim(); });
var filtered = new URLSearchParams();
allowed.forEach(function(k) {
body.getAll(k).forEach(function(v) { filtered.append(k, v); });
});
body = filtered;
}
}
// sx-include
var includeSel = el.getAttribute("sx-include");
if (includeSel && method !== "GET") {
if (!body) body = new URLSearchParams();
document.querySelectorAll(includeSel).forEach(function(inp) {
if (inp.name) body.append(inp.name, inp.value);
});
}
// sx-vals
var valsAttr = el.getAttribute("sx-vals");
if (valsAttr) {
try {
var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr);
if (method === "GET") {
for (var vk in vals) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]);
} else if (body instanceof URLSearchParams) {
for (var vk2 in vals) body.append(vk2, vals[vk2]);
} else if (!body) {
body = new URLSearchParams();
for (var vk3 in vals) body.append(vk3, vals[vk3]);
ct = "application/x-www-form-urlencoded";
}
} catch (e) {}
}
// GET form data → URL
if (method === "GET") {
var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form2) {
var qs = new URLSearchParams(new FormData(form2)).toString();
if (qs) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + qs;
}
if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) {
finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value);
}
}
return { body: body, url: finalUrl, "content-type": ct };
}
// --- Loading state ---
function showIndicator(el) {
if (!_hasDom) return NIL;
var sel = el.getAttribute("sx-indicator");
var ind = sel ? (document.querySelector(sel) || el.closest(sel)) : null;
if (ind) { ind.classList.add("sx-request"); ind.style.display = ""; }
return ind || NIL;
}
function disableElements(el) {
if (!_hasDom) return [];
var sel = el.getAttribute("sx-disabled-elt");
if (!sel) return [];
var elts = Array.prototype.slice.call(document.querySelectorAll(sel));
elts.forEach(function(e) { e.disabled = true; });
return elts;
}
function clearLoadingState(el, indicator, disabledElts) {
el.classList.remove("sx-request");
el.removeAttribute("aria-busy");
if (indicator && !isNil(indicator)) {
indicator.classList.remove("sx-request");
indicator.style.display = "none";
}
if (disabledElts) {
for (var i = 0; i < disabledElts.length; i++) disabledElts[i].disabled = false;
}
}
// --- DOM extras ---
function domQueryById(id) {
return _hasDom ? document.getElementById(id) : null;
}
function domMatches(el, sel) {
return el && el.matches ? el.matches(sel) : false;
}
function domClosest(el, sel) {
return el && el.closest ? el.closest(sel) : null;
}
function domBody() {
return _hasDom ? document.body : null;
}
function domHasClass(el, cls) {
return el && el.classList ? el.classList.contains(cls) : false;
}
function domAppendToHead(el) {
if (_hasDom && document.head) document.head.appendChild(el);
}
function domParseHtmlDocument(text) {
if (!_hasDom) return null;
return new DOMParser().parseFromString(text, "text/html");
}
function domOuterHtml(el) {
return el ? el.outerHTML : "";
}
function domBodyInnerHtml(doc) {
return doc && doc.body ? doc.body.innerHTML : "";
}
// --- Events ---
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; }
function domAddListener(el, event, fn, opts) {
if (!el || !el.addEventListener) return;
var o = {};
if (opts && !isNil(opts)) {
if (opts.once || opts["once"]) o.once = true;
}
el.addEventListener(event, fn, o);
}
// --- Validation ---
function validateForRequest(el) {
if (!_hasDom) return true;
var attr = el.getAttribute("sx-validate");
if (attr === null) {
var vForm = el.closest("[sx-validate]");
if (vForm) attr = vForm.getAttribute("sx-validate");
}
if (attr === null) return true; // no validation configured
var form = el.tagName === "FORM" ? el : el.closest("form");
if (form && !form.reportValidity()) return false;
if (attr && attr !== "true" && attr !== "") {
var fn = window[attr];
if (typeof fn === "function" && !fn(el)) return false;
}
return true;
}
// --- View Transitions ---
function withTransition(enabled, fn) {
if (enabled && _hasDom && document.startViewTransition) {
document.startViewTransition(fn);
} else {
fn();
}
}
// --- IntersectionObserver ---
function observeIntersection(el, fn, once, delay) {
if (!_hasDom || !("IntersectionObserver" in window)) { fn(); return; }
var fired = false;
var d = isNil(delay) ? 0 : delay;
var obs = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (!entry.isIntersecting) return;
if (once && fired) return;
fired = true;
if (once) obs.unobserve(el);
if (d) setTimeout(fn, d); else fn();
});
});
obs.observe(el);
}
// --- EventSource ---
function eventSourceConnect(url, el) {
var source = new EventSource(url);
source.addEventListener("error", function() { domDispatch(el, "sx:sseError", {}); });
source.addEventListener("open", function() { domDispatch(el, "sx:sseOpen", {}); });
if (typeof MutationObserver !== "undefined") {
var obs = new MutationObserver(function() {
if (!document.body.contains(el)) { source.close(); obs.disconnect(); }
});
obs.observe(document.body, { childList: true, subtree: true });
}
return source;
}
function eventSourceListen(source, event, fn) {
source.addEventListener(event, function(e) { fn(e.data); });
}
// --- Boost bindings ---
function bindBoostLink(el, href) {
el.addEventListener("click", function(e) {
e.preventDefault();
executeRequest(el, { method: "GET", url: href }).then(function() {
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
});
});
}
function bindBoostForm(form, method, action) {
form.addEventListener("submit", function(e) {
e.preventDefault();
executeRequest(form, { method: method, url: action }).then(function() {
try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {}
});
});
}
// --- Inline handlers ---
function bindInlineHandler(el, eventName, body) {
el.addEventListener(eventName, new Function("event", body));
}
// --- Preload binding ---
function bindPreload(el, events, debounceMs, fn) {
var timer = null;
events.forEach(function(evt) {
el.addEventListener(evt, function() {
if (debounceMs) {
clearTimeout(timer);
timer = setTimeout(fn, debounceMs);
} else {
fn();
}
});
});
}
// --- Processing markers ---
var PROCESSED = "_sxBound";
function markProcessed(el, key) { el[PROCESSED + key] = true; }
function isProcessed(el, key) { return !!el[PROCESSED + key]; }
// --- Script cloning ---
function createScriptClone(dead) {
var live = document.createElement("script");
for (var i = 0; i < dead.attributes.length; i++)
live.setAttribute(dead.attributes[i].name, dead.attributes[i].value);
live.textContent = dead.textContent;
return live;
}
// --- SX API references ---
function sxRender(source) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.render) return SxObj.render(source);
throw new Error("No SX renderer available");
}
function sxProcessScripts(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.processScripts) SxObj.processScripts(root || undefined);
}
function sxHydrate(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.hydrate) SxObj.hydrate(root || undefined);
}
function loadedComponentNames() {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (!SxObj) return [];
var env = SxObj.componentEnv || (SxObj.getEnv ? SxObj.getEnv() : {});
return Object.keys(env).filter(function(k) { return k.charAt(0) === "~"; });
}
// --- Response processing ---
function stripComponentScripts(text) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
return text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function extractResponseCss(text) {
if (!_hasDom) return text;
var target = document.getElementById("sx-css");
if (!target) return text;
return text.replace(/<style[^>]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi,
function(_, css) { target.textContent += css; return ""; });
}
function selectFromContainer(container, sel) {
var frag = document.createDocumentFragment();
sel.split(",").forEach(function(s) {
container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); });
});
return frag;
}
function childrenToFragment(container) {
var frag = document.createDocumentFragment();
while (container.firstChild) frag.appendChild(container.firstChild);
return frag;
}
function selectHtmlFromDoc(doc, sel) {
var parts = sel.split(",").map(function(s) { return s.trim(); });
var frags = [];
parts.forEach(function(s) {
doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); });
});
return frags.join("");
}
// --- Parsing ---
function tryParseJson(s) {
if (!s) return NIL;
try { return JSON.parse(s); } catch (e) { return NIL; }
}
// =========================================================================
// Post-transpilation fixups
@@ -1791,9 +2666,30 @@
morphNode: typeof morphNode === "function" ? morphNode : null,
morphChildren: typeof morphChildren === "function" ? morphChildren : null,
swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null,
process: typeof processElements === "function" ? processElements : null,
executeRequest: typeof executeRequest === "function" ? executeRequest : null,
postSwap: typeof postSwap === "function" ? postSwap : null,
init: typeof engineInit === "function" ? engineInit : null,
_version: "ref-2.0 (dom+engine+html+sx, bootstrap-compiled)"
};
// --- Popstate listener ---
if (typeof window !== "undefined") {
window.addEventListener("popstate", function(e) {
handlePopstate(e && e.state ? e.state.scrollY || 0 : 0);
});
}
// --- Auto-init ---
if (typeof document !== "undefined") {
var _sxRefInit = function() { engineInit(); };
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", _sxRefInit);
} else {
_sxRefInit();
}
}
if (typeof module !== "undefined" && module.exports) module.exports = SxRef;
else global.SxRef = SxRef;

View File

@@ -297,6 +297,92 @@ class JSEmitter:
"should-boost-link?": "shouldBoostLink",
"should-boost-form?": "shouldBoostForm",
"parse-sse-swap": "parseSseSwap",
# engine.sx orchestration
"_preload-cache": "_preloadCache",
"_css-hash": "_cssHash",
"dispatch-trigger-events": "dispatchTriggerEvents",
"init-css-tracking": "initCssTracking",
"execute-request": "executeRequest",
"do-fetch": "doFetch",
"handle-fetch-success": "handleFetchSuccess",
"handle-sx-response": "handleSxResponse",
"handle-html-response": "handleHtmlResponse",
"handle-retry": "handleRetry",
"bind-triggers": "bindTriggers",
"bind-event": "bindEvent",
"post-swap": "postSwap",
"activate-scripts": "activateScripts",
"process-oob-swaps": "processOobSwaps",
"hoist-head-elements": "hoistHeadElements",
"process-boosted": "processBoosted",
"boost-descendants": "boostDescendants",
"process-sse": "processSse",
"bind-sse": "bindSse",
"bind-sse-swap": "bindSseSwap",
"bind-inline-handlers": "bindInlineHandlers",
"bind-preload-for": "bindPreloadFor",
"do-preload": "doPreload",
"VERB_SELECTOR": "VERB_SELECTOR",
"process-elements": "processElements",
"process-one": "processOne",
"handle-popstate": "handlePopstate",
"engine-init": "engineInit",
# engine orchestration platform
"promise-resolve": "promiseResolve",
"promise-catch": "promiseCatch",
"abort-previous": "abortPrevious",
"track-controller": "trackController",
"new-abort-controller": "newAbortController",
"controller-signal": "controllerSignal",
"abort-error?": "isAbortError",
"set-timeout": "setTimeout_",
"set-interval": "setInterval_",
"clear-timeout": "clearTimeout_",
"request-animation-frame": "requestAnimationFrame_",
"csrf-token": "csrfToken",
"cross-origin?": "isCrossOrigin",
"loaded-component-names": "loadedComponentNames",
"build-request-body": "buildRequestBody",
"show-indicator": "showIndicator",
"disable-elements": "disableElements",
"clear-loading-state": "clearLoadingState",
"fetch-request": "fetchRequest",
"fetch-location": "fetchLocation",
"fetch-and-restore": "fetchAndRestore",
"fetch-preload": "fetchPreload",
"dom-query-by-id": "domQueryById",
"dom-matches?": "domMatches",
"dom-closest": "domClosest",
"dom-body": "domBody",
"dom-has-class?": "domHasClass",
"dom-append-to-head": "domAppendToHead",
"dom-parse-html-document": "domParseHtmlDocument",
"dom-outer-html": "domOuterHtml",
"dom-body-inner-html": "domBodyInnerHtml",
"prevent-default": "preventDefault_",
"element-value": "elementValue",
"validate-for-request": "validateForRequest",
"with-transition": "withTransition",
"observe-intersection": "observeIntersection",
"event-source-connect": "eventSourceConnect",
"event-source-listen": "eventSourceListen",
"bind-boost-link": "bindBoostLink",
"bind-boost-form": "bindBoostForm",
"bind-inline-handler": "bindInlineHandler",
"bind-preload": "bindPreload",
"mark-processed!": "markProcessed",
"is-processed?": "isProcessed",
"create-script-clone": "createScriptClone",
"sx-render": "sxRender",
"sx-process-scripts": "sxProcessScripts",
"sx-hydrate": "sxHydrate",
"strip-component-scripts": "stripComponentScripts",
"extract-response-css": "extractResponseCss",
"select-from-container": "selectFromContainer",
"children-to-fragment": "childrenToFragment",
"select-html-from-doc": "selectHtmlFromDoc",
"try-parse-json": "tryParseJson",
"process-css-response": "processCssResponse",
"browser-location-href": "browserLocationHref",
"browser-same-origin?": "browserSameOrigin",
"browser-push-state": "browserPushState",
@@ -1360,6 +1446,8 @@ PLATFORM_ENGINE_JS = """
// Platform interface — Engine (browser-only)
// =========================================================================
// --- Browser/Network ---
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
@@ -1420,6 +1508,531 @@ PLATFORM_ENGINE_JS = """
return JSON.parse(s);
} catch (e) { return null; }
}
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute("content") : NIL;
}
function isCrossOrigin(url) {
try {
var h = new URL(url, location.href).hostname;
return h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0);
} catch (e) { return false; }
}
// --- Promises ---
function promiseResolve(val) { return Promise.resolve(val); }
function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; }
// --- Abort controllers ---
var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
function abortPrevious(el) {
if (_controllers) {
var prev = _controllers.get(el);
if (prev) prev.abort();
}
}
function trackController(el, ctrl) {
if (_controllers) _controllers.set(el, ctrl);
}
function newAbortController() {
return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} };
}
function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; }
function isAbortError(err) { return err && err.name === "AbortError"; }
// --- Timers ---
function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); }
function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); }
function clearTimeout_(id) { clearTimeout(id); }
function requestAnimationFrame_(fn) {
if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(fn);
else setTimeout(fn, 16);
}
// --- Fetch ---
function fetchRequest(config, successFn, errorFn) {
var opts = { method: config.method, headers: config.headers };
if (config.signal) opts.signal = config.signal;
if (config.body && config.method !== "GET") opts.body = config.body;
if (config["cross-origin"]) opts.credentials = "include";
var p = config.preloaded
? Promise.resolve({
ok: true, status: 200,
headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }),
text: function() { return Promise.resolve(config.preloaded.text); }
})
: fetch(config.url, opts);
return p.then(function(resp) {
return resp.text().then(function(text) {
var getHeader = function(name) {
var v = resp.headers.get(name);
return v === null ? NIL : v;
};
return successFn(resp.ok, resp.status, getHeader, text);
});
}).catch(function(err) {
return errorFn(err);
});
}
function fetchLocation(headerVal) {
if (!_hasDom) return;
var locUrl = headerVal;
try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {}
fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) {
return r.text().then(function(t) {
var main = document.getElementById("main-panel");
if (main) {
main.innerHTML = t;
postSwap(main);
try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {}
}
});
});
}
function fetchAndRestore(main, url, headers, scrollY) {
var opts = { headers: headers };
try {
var h = new URL(url, location.href).hostname;
if (h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) {
opts.credentials = "include";
}
} catch (e) {}
fetch(url, opts).then(function(resp) {
return resp.text().then(function(text) {
text = stripComponentScripts(text);
text = extractResponseCss(text);
text = text.trim();
if (text.charAt(0) === "(") {
try {
var dom = sxRender(text);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(main, newMain || container);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} catch (err) {
console.error("sx-ref popstate error:", err);
location.reload();
}
} else {
var parser = new DOMParser();
var doc = parser.parseFromString(text, "text/html");
var newMain = doc.getElementById("main-panel");
if (newMain) {
morphChildren(main, newMain);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} else {
location.reload();
}
}
});
}).catch(function() { location.reload(); });
}
function fetchPreload(url, headers, cache) {
fetch(url, { headers: headers }).then(function(resp) {
if (!resp.ok) return;
var ct = resp.headers.get("Content-Type") || "";
return resp.text().then(function(text) {
preloadCacheSet(cache, url, text, ct);
});
}).catch(function() { /* ignore */ });
}
// --- Request body building ---
function buildRequestBody(el, method, url) {
if (!_hasDom) return { body: null, url: url, "content-type": NIL };
var body = null;
var ct = NIL;
var finalUrl = url;
var isJson = el.getAttribute("sx-encoding") === "json";
if (method !== "GET") {
var form = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form) {
if (isJson) {
var fd = new FormData(form);
var obj = {};
fd.forEach(function(v, k) {
if (obj[k] !== undefined) {
if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
obj[k].push(v);
} else { obj[k] = v; }
});
body = JSON.stringify(obj);
ct = "application/json";
} else {
body = new URLSearchParams(new FormData(form));
ct = "application/x-www-form-urlencoded";
}
}
}
// sx-params
var paramsSpec = el.getAttribute("sx-params");
if (paramsSpec && body instanceof URLSearchParams) {
if (paramsSpec === "none") {
body = new URLSearchParams();
} else if (paramsSpec.indexOf("not ") === 0) {
paramsSpec.substring(4).split(",").forEach(function(k) { body.delete(k.trim()); });
} else if (paramsSpec !== "*") {
var allowed = paramsSpec.split(",").map(function(s) { return s.trim(); });
var filtered = new URLSearchParams();
allowed.forEach(function(k) {
body.getAll(k).forEach(function(v) { filtered.append(k, v); });
});
body = filtered;
}
}
// sx-include
var includeSel = el.getAttribute("sx-include");
if (includeSel && method !== "GET") {
if (!body) body = new URLSearchParams();
document.querySelectorAll(includeSel).forEach(function(inp) {
if (inp.name) body.append(inp.name, inp.value);
});
}
// sx-vals
var valsAttr = el.getAttribute("sx-vals");
if (valsAttr) {
try {
var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr);
if (method === "GET") {
for (var vk in vals) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]);
} else if (body instanceof URLSearchParams) {
for (var vk2 in vals) body.append(vk2, vals[vk2]);
} else if (!body) {
body = new URLSearchParams();
for (var vk3 in vals) body.append(vk3, vals[vk3]);
ct = "application/x-www-form-urlencoded";
}
} catch (e) {}
}
// GET form data → URL
if (method === "GET") {
var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form2) {
var qs = new URLSearchParams(new FormData(form2)).toString();
if (qs) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + qs;
}
if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) {
finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value);
}
}
return { body: body, url: finalUrl, "content-type": ct };
}
// --- Loading state ---
function showIndicator(el) {
if (!_hasDom) return NIL;
var sel = el.getAttribute("sx-indicator");
var ind = sel ? (document.querySelector(sel) || el.closest(sel)) : null;
if (ind) { ind.classList.add("sx-request"); ind.style.display = ""; }
return ind || NIL;
}
function disableElements(el) {
if (!_hasDom) return [];
var sel = el.getAttribute("sx-disabled-elt");
if (!sel) return [];
var elts = Array.prototype.slice.call(document.querySelectorAll(sel));
elts.forEach(function(e) { e.disabled = true; });
return elts;
}
function clearLoadingState(el, indicator, disabledElts) {
el.classList.remove("sx-request");
el.removeAttribute("aria-busy");
if (indicator && !isNil(indicator)) {
indicator.classList.remove("sx-request");
indicator.style.display = "none";
}
if (disabledElts) {
for (var i = 0; i < disabledElts.length; i++) disabledElts[i].disabled = false;
}
}
// --- DOM extras ---
function domQueryById(id) {
return _hasDom ? document.getElementById(id) : null;
}
function domMatches(el, sel) {
return el && el.matches ? el.matches(sel) : false;
}
function domClosest(el, sel) {
return el && el.closest ? el.closest(sel) : null;
}
function domBody() {
return _hasDom ? document.body : null;
}
function domHasClass(el, cls) {
return el && el.classList ? el.classList.contains(cls) : false;
}
function domAppendToHead(el) {
if (_hasDom && document.head) document.head.appendChild(el);
}
function domParseHtmlDocument(text) {
if (!_hasDom) return null;
return new DOMParser().parseFromString(text, "text/html");
}
function domOuterHtml(el) {
return el ? el.outerHTML : "";
}
function domBodyInnerHtml(doc) {
return doc && doc.body ? doc.body.innerHTML : "";
}
// --- Events ---
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; }
function domAddListener(el, event, fn, opts) {
if (!el || !el.addEventListener) return;
var o = {};
if (opts && !isNil(opts)) {
if (opts.once || opts["once"]) o.once = true;
}
el.addEventListener(event, fn, o);
}
// --- Validation ---
function validateForRequest(el) {
if (!_hasDom) return true;
var attr = el.getAttribute("sx-validate");
if (attr === null) {
var vForm = el.closest("[sx-validate]");
if (vForm) attr = vForm.getAttribute("sx-validate");
}
if (attr === null) return true; // no validation configured
var form = el.tagName === "FORM" ? el : el.closest("form");
if (form && !form.reportValidity()) return false;
if (attr && attr !== "true" && attr !== "") {
var fn = window[attr];
if (typeof fn === "function" && !fn(el)) return false;
}
return true;
}
// --- View Transitions ---
function withTransition(enabled, fn) {
if (enabled && _hasDom && document.startViewTransition) {
document.startViewTransition(fn);
} else {
fn();
}
}
// --- IntersectionObserver ---
function observeIntersection(el, fn, once, delay) {
if (!_hasDom || !("IntersectionObserver" in window)) { fn(); return; }
var fired = false;
var d = isNil(delay) ? 0 : delay;
var obs = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (!entry.isIntersecting) return;
if (once && fired) return;
fired = true;
if (once) obs.unobserve(el);
if (d) setTimeout(fn, d); else fn();
});
});
obs.observe(el);
}
// --- EventSource ---
function eventSourceConnect(url, el) {
var source = new EventSource(url);
source.addEventListener("error", function() { domDispatch(el, "sx:sseError", {}); });
source.addEventListener("open", function() { domDispatch(el, "sx:sseOpen", {}); });
if (typeof MutationObserver !== "undefined") {
var obs = new MutationObserver(function() {
if (!document.body.contains(el)) { source.close(); obs.disconnect(); }
});
obs.observe(document.body, { childList: true, subtree: true });
}
return source;
}
function eventSourceListen(source, event, fn) {
source.addEventListener(event, function(e) { fn(e.data); });
}
// --- Boost bindings ---
function bindBoostLink(el, href) {
el.addEventListener("click", function(e) {
e.preventDefault();
executeRequest(el, { method: "GET", url: href }).then(function() {
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
});
});
}
function bindBoostForm(form, method, action) {
form.addEventListener("submit", function(e) {
e.preventDefault();
executeRequest(form, { method: method, url: action }).then(function() {
try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {}
});
});
}
// --- Inline handlers ---
function bindInlineHandler(el, eventName, body) {
el.addEventListener(eventName, new Function("event", body));
}
// --- Preload binding ---
function bindPreload(el, events, debounceMs, fn) {
var timer = null;
events.forEach(function(evt) {
el.addEventListener(evt, function() {
if (debounceMs) {
clearTimeout(timer);
timer = setTimeout(fn, debounceMs);
} else {
fn();
}
});
});
}
// --- Processing markers ---
var PROCESSED = "_sxBound";
function markProcessed(el, key) { el[PROCESSED + key] = true; }
function isProcessed(el, key) { return !!el[PROCESSED + key]; }
// --- Script cloning ---
function createScriptClone(dead) {
var live = document.createElement("script");
for (var i = 0; i < dead.attributes.length; i++)
live.setAttribute(dead.attributes[i].name, dead.attributes[i].value);
live.textContent = dead.textContent;
return live;
}
// --- SX API references ---
function sxRender(source) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.render) return SxObj.render(source);
throw new Error("No SX renderer available");
}
function sxProcessScripts(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.processScripts) SxObj.processScripts(root || undefined);
}
function sxHydrate(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (SxObj && SxObj.hydrate) SxObj.hydrate(root || undefined);
}
function loadedComponentNames() {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
if (!SxObj) return [];
var env = SxObj.componentEnv || (SxObj.getEnv ? SxObj.getEnv() : {});
return Object.keys(env).filter(function(k) { return k.charAt(0) === "~"; });
}
// --- Response processing ---
function stripComponentScripts(text) {
var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null);
return text.replace(/<script[^>]*type="text\\/sx"[^>]*data-components[^>]*>([\\s\\S]*?)<\\/script>/gi,
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function extractResponseCss(text) {
if (!_hasDom) return text;
var target = document.getElementById("sx-css");
if (!target) return text;
return text.replace(/<style[^>]*data-sx-css[^>]*>([\\s\\S]*?)<\\/style>/gi,
function(_, css) { target.textContent += css; return ""; });
}
function selectFromContainer(container, sel) {
var frag = document.createDocumentFragment();
sel.split(",").forEach(function(s) {
container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); });
});
return frag;
}
function childrenToFragment(container) {
var frag = document.createDocumentFragment();
while (container.firstChild) frag.appendChild(container.firstChild);
return frag;
}
function selectHtmlFromDoc(doc, sel) {
var parts = sel.split(",").map(function(s) { return s.trim(); });
var frags = [];
parts.forEach(function(s) {
doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); });
});
return frags.join("");
}
// --- Parsing ---
function tryParseJson(s) {
if (!s) return NIL;
try { return JSON.parse(s); } catch (e) { return NIL; }
}
"""
def fixups_js(has_html, has_sx, has_dom):
@@ -1642,10 +2255,33 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, adapter_label):
api_lines.append(' morphNode: typeof morphNode === "function" ? morphNode : null,')
api_lines.append(' morphChildren: typeof morphChildren === "function" ? morphChildren : null,')
api_lines.append(' swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null,')
api_lines.append(' process: typeof processElements === "function" ? processElements : null,')
api_lines.append(' executeRequest: typeof executeRequest === "function" ? executeRequest : null,')
api_lines.append(' postSwap: typeof postSwap === "function" ? postSwap : null,')
api_lines.append(' init: typeof engineInit === "function" ? engineInit : null,')
api_lines.append(f' _version: "{version}"')
api_lines.append(' };')
api_lines.append('')
if has_engine:
api_lines.append('''
// --- Popstate listener ---
if (typeof window !== "undefined") {
window.addEventListener("popstate", function(e) {
handlePopstate(e && e.state ? e.state.scrollY || 0 : 0);
});
}
// --- Auto-init ---
if (typeof document !== "undefined") {
var _sxRefInit = function() { engineInit(); };
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", _sxRefInit);
} else {
_sxRefInit();
}
}''')
api_lines.append(' if (typeof module !== "undefined" && module.exports) module.exports = SxRef;')
api_lines.append(' else global.SxRef = SxRef;')

View File

@@ -662,11 +662,684 @@
(or (dom-get-attr el "sx-sse-swap") "message")))
;; ==========================================================================
;; Engine orchestration
;;
;; The following functions define the runtime behavior of the engine:
;; request execution, trigger binding, post-swap lifecycle, boost, SSE,
;; and main processing. Browser-specific mechanics (fetch, addEventListener,
;; IntersectionObserver, EventSource, etc.) are declared as platform
;; interface at the bottom.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; Engine state
;; --------------------------------------------------------------------------
(define _preload-cache (dict))
(define _css-hash "")
;; --------------------------------------------------------------------------
;; Event dispatch helpers
;; --------------------------------------------------------------------------
(define dispatch-trigger-events
(fn (el header-val)
;; Parse and dispatch SX-Trigger header events.
;; Value: JSON object, JSON string, or comma-separated names.
(when header-val
(let ((parsed (try-parse-json header-val)))
(if (and parsed (dict? parsed))
(for-each
(fn (key) (dom-dispatch el key (dict-get parsed key)))
(keys parsed))
(for-each
(fn (name)
(let ((n (trim name)))
(when (not (= n ""))
(dom-dispatch el n (dict)))))
(split header-val ",")))))))
;; --------------------------------------------------------------------------
;; CSS tracking
;; --------------------------------------------------------------------------
(define init-css-tracking
(fn ()
;; Read CSS hash from <meta name="sx-css-classes">
(let ((meta (dom-query "meta[name=\"sx-css-classes\"]")))
(when meta
(let ((content (dom-get-attr meta "content")))
(when content
(set! _css-hash content)))))))
;; --------------------------------------------------------------------------
;; Request execution
;; --------------------------------------------------------------------------
(define execute-request
(fn (el verb-info extra-params)
;; Pre-flight gate logic: media, confirm, prompt.
;; Returns a promise.
(let ((current-verb (get-verb-info el))
(verb (if current-verb current-verb verb-info))
(method (get verb "method"))
(url (get verb "url")))
;; Reset retry backoff on fresh requests
(when (not (dom-has-class? el "sx-error"))
(dom-remove-attr el "data-sx-retry-ms"))
;; Gate: media query
(if (let ((media (dom-get-attr el "sx-media")))
(and media (not (browser-media-matches? media))))
(promise-resolve nil)
;; Gate: confirm dialog
(if (let ((msg (dom-get-attr el "sx-confirm")))
(and msg (not (browser-confirm msg))))
(promise-resolve nil)
;; Gate: prompt dialog
(let ((prompt-msg (dom-get-attr el "sx-prompt"))
(params extra-params))
(if prompt-msg
(let ((prompt-val (browser-prompt prompt-msg)))
(if (nil? prompt-val)
(promise-resolve nil)
(do
(set! params (or params (dict)))
(dict-set! params "promptValue" prompt-val)
(do-fetch el verb method url params))))
(do-fetch el verb method url params))))))))
;; --------------------------------------------------------------------------
;; Fetch pipeline
;; --------------------------------------------------------------------------
(define do-fetch
(fn (el verb method url extra-params)
;; Build request, execute fetch, handle response.
;; Returns a promise.
(let ((sync-attr (dom-get-attr el "sx-sync")))
(when (and sync-attr (contains? sync-attr "replace"))
(abort-previous el))
(let ((ctrl (new-abort-controller)))
(track-controller el ctrl)
(let ((headers (build-request-headers el
(loaded-component-names) _css-hash)))
;; Prompt header
(when (and extra-params (dict-has? extra-params "promptValue"))
(dict-set! headers "SX-Prompt"
(get extra-params "promptValue")))
;; CSRF for mutating same-origin
(when (and (not (= method "GET")) (browser-same-origin? url))
(let ((csrf (csrf-token)))
(when csrf
(dict-set! headers "X-CSRFToken" csrf))))
;; Build request body
(let ((body-info (build-request-body el method url)))
(let ((body (get body-info "body"))
(final-url (get body-info "url"))
(ct (get body-info "content-type")))
(when ct (dict-set! headers "Content-Type" ct))
;; Lifecycle: beforeRequest
(if (not (dom-dispatch el "sx:beforeRequest"
(dict "method" method "url" final-url)))
(promise-resolve nil)
(do
;; Loading state
(dom-add-class el "sx-request")
(dom-set-attr el "aria-busy" "true")
(let ((indicator (show-indicator el))
(disabled-elts (disable-elements el))
(preloaded (if (= method "GET")
(preload-cache-get _preload-cache final-url)
nil)))
;; Platform fetch with callbacks
(fetch-request
(dict "url" final-url "method" method
"headers" headers "body" body
"signal" (controller-signal ctrl)
"preloaded" preloaded
"cross-origin" (cross-origin? final-url))
;; Success: (fn (resp-ok status get-header text) ...)
(fn (resp-ok status get-header text)
(do
(clear-loading-state el indicator disabled-elts)
(if (not resp-ok)
(do
(dom-dispatch el "sx:responseError"
(dict "status" status))
(handle-retry el verb extra-params))
(do
(dom-dispatch el "sx:afterRequest" (dict))
(handle-fetch-success el final-url verb
extra-params get-header text)))))
;; Error: (fn (err) ...)
(fn (err)
(do
(clear-loading-state el indicator disabled-elts)
(when (not (abort-error? err))
(do
(dom-dispatch el "sx:sendError"
(dict "error" err))
(handle-retry el verb extra-params))))))))))))))))
;; --------------------------------------------------------------------------
;; Response handling
;; --------------------------------------------------------------------------
(define handle-fetch-success
(fn (el url verb extra-params get-header text)
;; Process a successful fetch response.
(let ((headers (process-response-headers get-header)))
;; Redirect — skip swap
(if (get headers "redirect")
(browser-navigate (get headers "redirect"))
;; Refresh — skip swap
(if (= (get headers "refresh") "true")
(browser-reload)
(do
;; Trigger events from header
(dispatch-trigger-events el (get headers "trigger"))
;; Determine swap target and strategy
(let ((raw-swap (or (dom-get-attr el "sx-swap") DEFAULT_SWAP))
(target (resolve-target el))
(select-sel (dom-get-attr el "sx-select")))
;; Server overrides
(when (get headers "retarget")
(set! target (or (dom-query (get headers "retarget")) target)))
(when (get headers "reswap")
(set! raw-swap (get headers "reswap")))
;; Parse swap spec
(let ((swap (parse-swap-spec raw-swap false))
(ct (or (get headers "content-type") "")))
;; Dispatch by content type
(if (contains? ct "text/sx")
(handle-sx-response el target swap select-sel text)
(handle-html-response el target swap select-sel text))
;; SX-Location
(when (get headers "location")
(fetch-location (get headers "location")))
;; History
(handle-history el url headers)
;; After-swap lifecycle
(dom-dispatch el "sx:afterSwap" (dict "target" target))
(dispatch-trigger-events el (get headers "trigger-swap"))
(request-animation-frame
(fn ()
(do
(dom-dispatch el "sx:afterSettle"
(dict "target" target))
(dispatch-trigger-events el
(get headers "trigger-settle")))))))))))))
;; --------------------------------------------------------------------------
;; SX response handler
;; --------------------------------------------------------------------------
(define handle-sx-response
(fn (el target swap select-sel text)
;; Process text/sx response: extract components, CSS, render, swap.
(let ((cleaned (strip-component-scripts text))
(cleaned2 (extract-response-css cleaned)))
(let ((source (trim cleaned2)))
(when (and source (not (= source "")))
(let ((dom (sx-render source))
(container (dom-create-element "div" nil)))
(dom-append container dom)
;; OOB processing on live DOM nodes
(process-oob-swaps container
(fn (t oob s) (swap-dom-nodes t oob s)))
;; Select filtering
(let ((selected (if select-sel
(select-from-container container select-sel)
(children-to-fragment container))))
;; Main swap
(when (and (not (= (get swap "style") "none")) target)
(with-transition (get swap "transition")
(fn ()
(do
(swap-dom-nodes target selected (get swap "style"))
(hoist-head-elements target))))))))))))
;; --------------------------------------------------------------------------
;; HTML response handler
;; --------------------------------------------------------------------------
(define handle-html-response
(fn (el target swap select-sel text)
;; Process HTML response: parse, scripts, OOB, swap.
(let ((doc (dom-parse-html-document text)))
;; Process sx scripts
(sx-process-scripts doc)
;; OOB processing
(process-oob-swaps doc
(fn (t oob s)
(swap-html-string t (dom-outer-html oob) s)))
;; Build content
(let ((content (if select-sel
(select-html-from-doc doc select-sel)
(or (dom-body-inner-html doc) text))))
;; Main swap
(when (and (not (= (get swap "style") "none")) target)
(with-transition (get swap "transition")
(fn ()
(do
(swap-html-string target content (get swap "style"))
(hoist-head-elements target)))))))))
;; --------------------------------------------------------------------------
;; Retry handling
;; --------------------------------------------------------------------------
(define handle-retry
(fn (el verb-info extra-params)
;; Retry failed request with exponential backoff.
(let ((retry-attr (dom-get-attr el "sx-retry")))
(when retry-attr
(let ((spec (parse-retry-spec retry-attr))
(current-ms (or (parse-int
(dom-get-attr el "data-sx-retry-ms") 0)
(get spec "start-ms"))))
(dom-add-class el "sx-error")
(dom-remove-class el "sx-loading")
(set-timeout
(fn ()
(do
(dom-remove-class el "sx-error")
(dom-add-class el "sx-loading")
(dom-set-attr el "data-sx-retry-ms"
(str (next-retry-ms current-ms (get spec "cap-ms"))))
(execute-request el verb-info extra-params)))
current-ms))))))
;; --------------------------------------------------------------------------
;; Trigger binding
;; --------------------------------------------------------------------------
(define bind-triggers
(fn (el verb-info)
;; Parse triggers and bind event handlers.
(let ((trigger-spec (dom-get-attr el "sx-trigger"))
(triggers (if trigger-spec
(parse-trigger-spec trigger-spec)
(default-trigger (dom-tag-name el)))))
(for-each
(fn (trig)
(let ((kind (classify-trigger trig)))
(cond
(= kind "poll")
(set-interval
(fn () (execute-request el verb-info nil))
(or (get (get trig "modifiers") "interval") 1000))
(= kind "intersect")
(observe-intersection el
(fn () (execute-request el verb-info nil))
(get (get trig "modifiers") "once")
(get (get trig "modifiers") "delay"))
(= kind "load")
(set-timeout
(fn () (execute-request el verb-info nil)) 0)
(= kind "revealed")
(observe-intersection el
(fn () (execute-request el verb-info nil))
true nil)
:else
(bind-event el verb-info trig))))
triggers))))
;; --------------------------------------------------------------------------
;; Event binding with modifiers
;; --------------------------------------------------------------------------
(define bind-event
(fn (el verb-info trig)
;; Bind a single event with modifiers (once, delay, changed, from).
(let ((event-name (get trig "event"))
(mods (get trig "modifiers"))
(listen-target (if (get mods "from")
(or (dom-query (get mods "from")) el)
el))
(timer nil)
(last-val nil))
(dom-add-listener listen-target event-name
(fn (e)
(do
;; Prevent defaults
(when (= event-name "submit") (prevent-default e))
(when (and (= event-name "click") (= (dom-tag-name el) "A"))
(prevent-default e))
;; Validation gate
(if (not (validate-for-request el))
(dom-dispatch el "sx:validationFailed" (dict))
;; Changed modifier gate
(if (and (get mods "changed")
(not (nil? (element-value el)))
(= (element-value el) last-val))
nil
(do
(when (get mods "changed")
(set! last-val (element-value el)))
;; Apply optimistic update
(let ((opt-state (apply-optimistic el))
(exec-fn
(fn ()
(let ((p (execute-request el verb-info nil)))
(when (and opt-state p)
(promise-catch p
(fn (_) (revert-optimistic opt-state))))))))
;; Delay modifier
(if (get mods "delay")
(do
(clear-timeout timer)
(set! timer
(set-timeout exec-fn (get mods "delay"))))
(exec-fn))))))))
(dict "once" (get mods "once"))))))
;; --------------------------------------------------------------------------
;; Post-swap lifecycle
;; --------------------------------------------------------------------------
(define post-swap
(fn (root)
;; Post-swap: activate scripts, load components, hydrate, bind engine.
(do
(activate-scripts root)
(sx-process-scripts root)
(sx-hydrate root)
(process-elements root))))
(define activate-scripts
(fn (root)
;; Scripts inserted via innerHTML don't execute.
;; Replace dead scripts with live clones so the browser runs them.
(let ((dead (dom-query-all root
"script:not([type]), script[type='text/javascript']")))
(for-each
(fn (d)
(let ((live (create-script-clone d)))
(dom-replace-child (dom-parent d) live d)))
dead))))
;; --------------------------------------------------------------------------
;; Out-of-band swap processing (orchestration variant)
;; --------------------------------------------------------------------------
(define process-oob-swaps
(fn (container swap-fn)
;; Find elements with sx-swap-oob/hx-swap-oob and swap to targets.
(for-each
(fn (attr)
(let ((oob-els (dom-query-all container (str "[" attr "]"))))
(for-each
(fn (oob)
(let ((swap-type (or (dom-get-attr oob attr) "outerHTML"))
(target-id (dom-id oob)))
(dom-remove-attr oob attr)
(when (dom-parent oob)
(dom-remove-child (dom-parent oob) oob))
(when target-id
(let ((target (dom-query-by-id target-id)))
(when target
(swap-fn target oob swap-type))))))
oob-els)))
(list "sx-swap-oob" "hx-swap-oob"))))
;; --------------------------------------------------------------------------
;; Head element hoisting
;; --------------------------------------------------------------------------
(define hoist-head-elements
(fn (root)
;; Move <style> and <link rel=stylesheet> from swapped content to <head>.
(let ((styles (dom-query-all root "style[data-sx-css]"))
(links (dom-query-all root "link[rel='stylesheet']")))
(for-each
(fn (el)
(when (dom-parent el)
(dom-remove-child (dom-parent el) el))
(dom-append-to-head el))
styles)
(for-each
(fn (el)
(when (dom-parent el)
(dom-remove-child (dom-parent el) el))
(dom-append-to-head el))
links))))
;; --------------------------------------------------------------------------
;; Boost processing
;; --------------------------------------------------------------------------
(define process-boosted
(fn (root)
;; Find sx-boost containers and boost their links/forms.
(let ((containers (dom-query-all root "[sx-boost]")))
(when (dom-matches? root "[sx-boost]")
(boost-descendants root))
(for-each boost-descendants containers))))
(define boost-descendants
(fn (container)
;; Boost links and forms inside a container.
(do
;; Boost links
(let ((links (dom-query-all container "a[href]")))
(for-each
(fn (link)
(when (and (not (is-processed? link "boost"))
(should-boost-link? link))
(mark-processed! link "boost")
(bind-boost-link link (dom-get-attr link "href"))
;; Default attrs for boosted links
(when (not (dom-has-attr? link "sx-target"))
(dom-set-attr link "sx-target" "#main-panel"))
(when (not (dom-has-attr? link "sx-swap"))
(dom-set-attr link "sx-swap" "innerHTML"))
(when (not (dom-has-attr? link "sx-select"))
(dom-set-attr link "sx-select" "#main-panel"))))
links))
;; Boost forms
(let ((forms (dom-query-all container "form")))
(for-each
(fn (form)
(when (and (not (is-processed? form "boost"))
(should-boost-form? form))
(mark-processed! form "boost")
(bind-boost-form form
(or (upper (dom-get-attr form "method")) "GET")
(or (dom-get-attr form "action")
(browser-location-href)))
(when (not (dom-has-attr? form "sx-target"))
(dom-set-attr form "sx-target" "#main-panel"))
(when (not (dom-has-attr? form "sx-swap"))
(dom-set-attr form "sx-swap" "innerHTML"))))
forms)))))
;; --------------------------------------------------------------------------
;; SSE (Server-Sent Events)
;; --------------------------------------------------------------------------
(define process-sse
(fn (root)
;; Find elements with sx-sse and bind EventSource connections.
(let ((sse-els (dom-query-all root "[sx-sse]")))
(when (dom-matches? root "[sx-sse]")
(bind-sse root))
(for-each bind-sse sse-els))))
(define bind-sse
(fn (el)
;; Connect to EventSource and bind swap handlers.
(when (not (is-processed? el "sse"))
(mark-processed! el "sse")
(let ((url (dom-get-attr el "sx-sse")))
(when url
(let ((source (event-source-connect url el)))
(let ((swap-els (dom-query-all el "[sx-sse-swap]")))
(when (dom-has-attr? el "sx-sse-swap")
(bind-sse-swap el source))
(for-each
(fn (child) (bind-sse-swap child source))
swap-els))))))))
(define bind-sse-swap
(fn (el source)
;; Bind SSE event handler for swap.
(let ((event-name (parse-sse-swap el)))
(event-source-listen source event-name
(fn (data)
(let ((target (or (resolve-target el) el))
(swap-style (or (dom-get-attr el "sx-swap") "innerHTML")))
(if (starts-with? (trim data) "(")
;; SX response — render to DOM
(let ((dom (sx-render data)))
(swap-dom-nodes target dom swap-style))
;; HTML response
(swap-html-string target data swap-style))
(post-swap target)
(dom-dispatch el "sx:sseMessage"
(dict "data" data "event" event-name))))))))
;; --------------------------------------------------------------------------
;; Inline event handlers
;; --------------------------------------------------------------------------
(define bind-inline-handlers
(fn (el)
;; Bind sx-on:* inline event handlers.
(when (not (is-processed? el "on"))
(mark-processed! el "on")
(let ((attrs (dom-attr-list el)))
(for-each
(fn (attr)
(let ((name (first attr))
(val (nth attr 1)))
(when (starts-with? name "sx-on:")
(bind-inline-handler el (slice name 6) val))))
attrs)))))
;; --------------------------------------------------------------------------
;; Preload
;; --------------------------------------------------------------------------
(define bind-preload-for
(fn (el)
;; Set up preload listeners on the element.
(when (dom-has-attr? el "sx-preload")
(let ((mode (or (dom-get-attr el "sx-preload") "mousedown"))
(events (if (= mode "mouseover")
(list "mouseenter" "focusin")
(list "mousedown" "focusin")))
(debounce-ms (if (= mode "mouseover") 100 0)))
(bind-preload el events debounce-ms
(fn ()
(let ((verb (get-verb-info el)))
(when verb
(let ((url (get verb "url")))
(when (nil? (preload-cache-get _preload-cache url))
(do-preload url)))))))))))
(define do-preload
(fn (url)
;; Preload a URL into the cache.
(let ((headers (build-request-headers nil
(loaded-component-names) _css-hash)))
(fetch-preload url headers _preload-cache))))
;; --------------------------------------------------------------------------
;; Main processing
;; --------------------------------------------------------------------------
(define VERB_SELECTOR
"[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]")
(define process-elements
(fn (root)
;; Main engine processing: find and bind all sx-* elements.
(let ((root (or root (dom-body))))
(when root
;; Process root itself
(when (dom-matches? root VERB_SELECTOR)
(process-one root))
;; Process descendants
(let ((elements (dom-query-all root VERB_SELECTOR)))
(for-each process-one elements))
;; Boost, SSE, inline handlers
(process-boosted root)
(process-sse root)
(let ((on-els (dom-query-all root
"[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:responseError]")))
(for-each bind-inline-handlers on-els))))))
(define process-one
(fn (el)
;; Process a single element: bind triggers and preload.
(when (not (is-processed? el "bound"))
;; Skip disabled elements
(when (not (or (dom-has-attr? el "sx-disable")
(dom-closest el "[sx-disable]")))
(mark-processed! el "bound")
(let ((verb-info (get-verb-info el)))
(when verb-info
(bind-triggers el verb-info)
(bind-preload-for el)))))))
;; --------------------------------------------------------------------------
;; History: popstate handling
;; --------------------------------------------------------------------------
(define handle-popstate
(fn (scroll-y)
;; Handle browser back/forward navigation.
(let ((url (browser-location-href))
(main (dom-query-by-id "main-panel")))
(if (not main)
(browser-reload)
(let ((headers (build-request-headers nil
(loaded-component-names) _css-hash)))
(dict-set! headers "SX-History-Restore" "true")
(fetch-and-restore main url headers scroll-y))))))
;; --------------------------------------------------------------------------
;; Engine initialization
;; --------------------------------------------------------------------------
(define engine-init
(fn ()
;; Initialize: CSS tracking, scripts, hydrate, process.
(do
(init-css-tracking)
(sx-process-scripts nil)
(sx-hydrate nil)
(process-elements nil))))
;; --------------------------------------------------------------------------
;; Platform interface — Engine
;; --------------------------------------------------------------------------
;;
;; Browser/Network:
;; === Browser/Network ===
;; (browser-location-href) → current URL string
;; (browser-same-origin? url) → boolean
;; (browser-push-state url) → void (history.pushState)
@@ -678,22 +1351,60 @@
;; (browser-confirm msg) → boolean
;; (browser-prompt msg) → string or nil
;; (now-ms) → current timestamp in milliseconds
;; (csrf-token) → string from meta[name=csrf-token]
;; (cross-origin? url) → boolean (needs credentials:include)
;;
;; Fetch:
;; (browser-fetch url opts) → Promise-like
;; (browser-abort-controller) → AbortController
;; === Promises ===
;; (promise-resolve val) resolved Promise
;; (promise-catch p fn) → p.catch(fn)
;;
;; DOM query:
;; === Abort controllers ===
;; (abort-previous el) → abort + remove controller for element
;; (track-controller el ctrl) → store controller for element
;; (new-abort-controller) → new AbortController()
;; (controller-signal ctrl) → ctrl.signal
;; (abort-error? err) → boolean (err.name === "AbortError")
;;
;; === Timers ===
;; (set-timeout fn ms) → timer id
;; (set-interval fn ms) → timer id
;; (clear-timeout id) → void
;; (request-animation-frame fn) → void
;;
;; === Fetch ===
;; (fetch-request config success-fn error-fn) → Promise
;; config: dict with url, method, headers, body, signal, preloaded,
;; cross-origin
;; success-fn: (fn (resp-ok status get-header text) ...)
;; error-fn: (fn (err) ...)
;; (fetch-location url) → fetch URL and swap to #main-panel
;; (fetch-and-restore main url headers scroll-y) → popstate fetch+swap
;; (fetch-preload url headers cache) → preload into cache
;;
;; === Request body ===
;; (build-request-body el method url) → dict with body, url, content-type
;; Handles FormData, JSON encoding, sx-params, sx-include, sx-vals
;;
;; === Loading state ===
;; (show-indicator el) → indicator state (or nil)
;; (disable-elements el) → list of disabled elements
;; (clear-loading-state el indicator-state disabled-elts) → void
;;
;; === DOM query (extended) ===
;; (dom-query sel) → Element or nil
;; (dom-query-all root sel) → list of Elements
;; (dom-query-by-id id) → Element or nil
;; (dom-id el) → string id or nil
;; (dom-parent el) → parent Element
;; (dom-first-child el) → first child node
;; (dom-next-sibling el) → next sibling node
;; (dom-child-list el) → list of child nodes
;; (dom-tag-name el) → uppercase tag name
;; (dom-matches? el sel) → boolean
;; (dom-closest el sel) → Element or nil
;; (dom-body) → document.body
;;
;; DOM mutation:
;; === DOM mutation ===
;; (dom-create-element tag ns) → Element
;; (dom-append parent child) → void
;; (dom-prepend parent child) → void
@@ -702,46 +1413,83 @@
;; (dom-remove-child parent child) → void
;; (dom-replace-child parent new old) → void
;; (dom-clone node) → deep clone
;; (dom-append-to-head el) → void
;;
;; DOM attributes:
;; === DOM attributes ===
;; (dom-get-attr el name) → string or nil
;; (dom-set-attr el name val) → void
;; (dom-remove-attr el name) → void
;; (dom-has-attr? el name) → boolean
;; (dom-attr-list el) → list of (name value) pairs
;;
;; DOM properties:
;; === DOM properties/style ===
;; (dom-get-prop el name) → value
;; (dom-set-prop el name val) → void
;; (dom-get-style el prop) → string
;; (dom-set-style el prop val) → void
;; (dom-add-class el cls) → void
;; (dom-remove-class el cls) → void
;; (dom-has-class? el cls) → boolean
;;
;; DOM inspection:
;; (dom-node-type el) → number (1=element, 3=text, 8=comment, 11=fragment)
;; (dom-node-name el) → string (uppercase tag or #text/#comment)
;; === DOM inspection ===
;; (dom-node-type el) → number
;; (dom-node-name el) → string
;; (dom-text-content el) → string
;; (dom-set-text-content el s) → void
;; (dom-is-fragment? el) → boolean (nodeType === 11)
;; (dom-is-fragment? el) → boolean
;; (dom-is-child-of? child parent) → boolean
;; (dom-is-active-element? el) → boolean (el === document.activeElement)
;; (dom-is-input-element? el) → boolean (INPUT/TEXTAREA/SELECT)
;; (dom-is-active-element? el) → boolean
;; (dom-is-input-element? el) → boolean
;;
;; DOM content:
;; === DOM content ===
;; (dom-set-inner-html el html) → void
;; (dom-insert-adjacent-html el pos html) → void
;; (dom-parse-html-string text) → parsed document
;; (dom-parse-html-document text) → parsed document (DOMParser)
;; (dom-outer-html el) → string
;; (dom-body-inner-html doc) → string
;;
;; Events:
;; === Events ===
;; (dom-dispatch el name detail) → boolean (dispatchEvent)
;; (dom-add-listener el event fn opts) → void
;; (dom-remove-listener el event fn) → void
;; (prevent-default e) → void
;; (element-value el) → el.value or nil
;;
;; Parsing:
;; (parse-header-value s) → dict (parse JSON or SX dict from string)
;; === Event binding (platform-level) ===
;; (bind-boost-link el href) → void (click handler + pushState)
;; (bind-boost-form form method action) → void (submit handler)
;; (bind-inline-handler el event-name body) → void (new Function)
;; (bind-preload el events debounce-ms fn) → void (preload listeners)
;; (observe-intersection el fn once? delay) → void (IntersectionObserver)
;; (event-source-connect url el) → EventSource (with cleanup)
;; (event-source-listen source event fn) → void
;; (validate-for-request el) → boolean (form validation)
;;
;; Misc:
;; === Processing markers ===
;; (mark-processed! el key) → void
;; (is-processed? el key) → boolean
;;
;; === Script handling ===
;; (create-script-clone script) → live script Element
;;
;; === SX API (references to Sx object) ===
;; (sx-render source) → DOM nodes (Sx.render)
;; (sx-process-scripts root) → void (Sx.processScripts)
;; (sx-hydrate root) → void (Sx.hydrate)
;; (loaded-component-names) → list of ~name strings
;;
;; === Response processing ===
;; (strip-component-scripts text) → cleaned text (regex strip + load)
;; (extract-response-css text) → cleaned text (regex strip + inject)
;; (select-from-container el sel) → DocumentFragment
;; (children-to-fragment el) → DocumentFragment
;; (select-html-from-doc doc sel) → HTML string
;; (with-transition enabled fn) → void (View Transition API)
;;
;; === Parsing ===
;; (parse-header-value s) → dict
;; (try-parse-json s) → parsed value or nil
;;
;; === Misc ===
;; (dict-has? d key) → boolean
;; (dict-delete! d key) → void
;; --------------------------------------------------------------------------