diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js
index 417bca8..c4288ca 100644
--- a/shared/static/scripts/sx-browser.js
+++ b/shared/static/scripts/sx-browser.js
@@ -1252,6 +1252,9 @@
// parse-sse-swap
var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); };
+
+ // === Transpiled from orchestration ===
+
// _preload-cache
var _preloadCache = {};
@@ -1261,15 +1264,15 @@
// 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);
+ return (isSxTruthy(parsed) ? forEach(function(key) { return domDispatch(el, key, get(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() {
+ var trimmed = trim(name);
+ return (isSxTruthy(!isEmpty(trimmed)) ? domDispatch(el, trimmed, {}) : NIL);
})(); }, split(headerVal, ",")));
})() : NIL); };
// init-css-tracking
var initCssTracking = function() { return (function() {
- var meta = domQuery("meta[name=\"sx-css-classes\"]");
+ var meta = domQuery("meta[name=\"sx-css-hash\"]");
return (isSxTruthy(meta) ? (function() {
var content = domGetAttr(meta, "content");
return (isSxTruthy(content) ? (_cssHash = content) : NIL);
@@ -1278,65 +1281,58 @@
// 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");
-}
+ var info = sxOr(verbInfo, getVerbInfo(el));
+ return (isSxTruthy(isNil(info)) ? promiseResolve(NIL) : (function() {
+ var verb = get(info, "method");
+ var url = get(info, "url");
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));
+ var confirmMsg = domGetAttr(el, "sx-confirm");
+ return (isSxTruthy(confirmMsg) && !browserConfirm(confirmMsg));
})()) ? 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));
+ var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL);
+ return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!validateForRequest(el)) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams))));
})()));
+})());
})(); };
// 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")))) {
+ var sync = domGetAttr(el, "sx-sync");
+ if (isSxTruthy((sync == "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 body = get(bodyInfo, "body");
var ct = get(bodyInfo, "content-type");
+ var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
+ var csrf = csrfToken();
+ if (isSxTruthy(extraParams)) {
+ { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } }
+}
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() {
+ if (isSxTruthy(csrf)) {
+ headers["X-CSRFToken"] = csrf;
+}
+ return (function() {
+ var cached = preloadCacheGet(_preloadCache, finalUrl);
+ var optimisticState = applyOptimistic(el);
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)); });
-})()));
-})();
+ domAddClass(el, "sx-request");
+ domSetAttr(el, "aria-busy", "true");
+ domDispatch(el, "sx:beforeRequest", {["url"]: finalUrl, ["method"]: method});
+ return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["cross-origin"]: isCrossOrigin(finalUrl), ["preloaded"]: cached}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!respOk) ? (domDispatch(el, "sx:responseError", {["status"]: status, ["text"]: text}), handleRetry(el, verb, method, finalUrl, extraParams)) : (domDispatch(el, "sx:afterRequest", {["status"]: status}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isAbortError(err)) ? domDispatch(el, "sx:requestError", {["error"]: err}) : NIL)); });
})();
})();
})();
@@ -1344,257 +1340,225 @@
// 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"))); });
+ var respHeaders = processResponseHeaders(getHeader);
+ (function() {
+ var newHash = get(respHeaders, "css-hash");
+ return (isSxTruthy(newHash) ? (_cssHash = newHash) : NIL);
})();
+ dispatchTriggerEvents(el, get(respHeaders, "trigger"));
+ return (isSxTruthy(get(respHeaders, "redirect")) ? browserNavigate(get(respHeaders, "redirect")) : (isSxTruthy(get(respHeaders, "refresh")) ? browserReload() : (isSxTruthy(get(respHeaders, "location")) ? fetchLocation(get(respHeaders, "location")) : (function() {
+ var targetEl = (isSxTruthy(get(respHeaders, "retarget")) ? domQuery(get(respHeaders, "retarget")) : resolveTarget(el));
+ var swapSpec = parseSwapSpec(sxOr(get(respHeaders, "reswap"), domGetAttr(el, "sx-swap")), domHasClass(domBody(), "sx-transitions"));
+ var swapStyle = get(swapSpec, "style");
+ var useTransition = get(swapSpec, "transition");
+ var ct = sxOr(get(respHeaders, "content-type"), "");
+ (isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, targetEl, text, swapStyle, useTransition) : handleHtmlResponse(el, targetEl, text, swapStyle, useTransition));
+ dispatchTriggerEvents(el, get(respHeaders, "trigger-swap"));
+ handleHistory(el, url, respHeaders);
+ if (isSxTruthy(get(respHeaders, "trigger-settle"))) {
+ setTimeout_(function() { return dispatchTriggerEvents(el, get(respHeaders, "trigger-settle")); }, 20);
+}
+ return domDispatch(el, "sx:afterSwap", {["target"]: targetEl, ["swap"]: swapStyle});
})())));
})(); };
// handle-sx-response
- var handleSxResponse = function(el, target, swap, selectSel, text) { return (function() {
+ var handleSxResponse = function(el, target, text, swapStyle, useTransition) { 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 final = extractResponseCss(cleaned);
+ return (function() {
+ var trimmed = trim(final);
+ return (isSxTruthy(!isEmpty(trimmed)) ? (function() {
+ var rendered = sxRender(trimmed);
var container = domCreateElement("div", NIL);
- domAppend(container, dom);
+ domAppend(container, rendered);
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);
+ var selectSel = domGetAttr(el, "sx-select");
+ var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container));
+ return withTransition(useTransition, function() { return swapDomNodes(target, content, swapStyle); });
})();
})() : NIL);
})();
+})();
})(); };
// handle-html-response
- var handleHtmlResponse = function(el, target, swap, selectSel, text) { return (function() {
+ var handleHtmlResponse = function(el, target, text, swapStyle, useTransition) { 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);
-})();
+ return (isSxTruthy(doc) ? (function() {
+ var selectSel = domGetAttr(el, "sx-select");
+ return (isSxTruthy(selectSel) ? (function() {
+ var html = selectHtmlFromDoc(doc, selectSel);
+ return withTransition(useTransition, function() { return swapHtmlString(target, html, swapStyle); });
+})() : (function() {
+ var container = domCreateElement("div", NIL);
+ domSetInnerHtml(container, domBodyInnerHtml(doc));
+ processOobSwaps(container, function(t, oob, s) { return swapDomNodes(t, oob, s); });
+ hoistHeadElements(container);
+ return withTransition(useTransition, function() { return swapDomNodes(target, childrenToFragment(container), swapStyle); });
+})());
+})() : NIL);
})(); };
// handle-retry
- var handleRetry = function(el, verbInfo, extraParams) { return (function() {
+ var handleRetry = function(el, verb, method, url, 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);
+ return (isSxTruthy(spec) ? (function() {
+ var currentMs = sxOr(domGetAttr(el, "data-sx-retry-ms"), get(spec, "start-ms"));
+ return (function() {
+ var ms = parseInt_(currentMs, get(spec, "start-ms"));
+ domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(ms, get(spec, "cap-ms")))));
+ return setTimeout_(function() { return doFetch(el, verb, method, url, extraParams); }, ms);
+})();
})() : 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)))));
+ var triggers = sxOr(parseTriggerSpec(domGetAttr(el, "sx-trigger")), defaultTrigger(domTagName(el)));
+ return forEach(function(trigger) { return (function() {
+ var kind = classifyTrigger(trigger);
+ var mods = get(trigger, "modifiers");
+ return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "interval")) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, false, get(mods, "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, NIL, NIL); }, sxOr(get(mods, "delay"), 0)) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, true, get(mods, "delay")) : (isSxTruthy((kind == "event")) ? bindEvent(el, get(trigger, "event"), mods, verbInfo) : NIL)))));
})(); }, 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 bindEvent = function(el, eventName, mods, verbInfo) { return (function() {
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")});
+ var listenTarget = (isSxTruthy(get(mods, "from")) ? domQuery(get(mods, "from")) : el);
+ return (isSxTruthy(listenTarget) ? domAddListener(listenTarget, eventName, function(e) { return (function() {
+ var shouldFire = true;
+ if (isSxTruthy(get(mods, "changed"))) {
+ (function() {
+ var val = elementValue(el);
+ return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
+})();
+}
+ return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL))) : NIL);
+})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
})(); };
// post-swap
- var postSwap = function(root) { return (activateScripts(root), sxProcessScripts(root), sxHydrate(root), processElements(root)); };
+ var postSwap = function(root) { return activateScripts(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);
-})(); };
+ var activateScripts = function(root) { return (isSxTruthy(root) ? (function() {
+ var scripts = domQueryAll(root, "script");
+ return forEach(function(dead) { return (isSxTruthy((isSxTruthy(!domHasAttr(dead, "data-components")) && !domHasAttr(dead, "data-sx-activated"))) ? (function() {
+ var live = createScriptClone(dead);
+ domSetAttr(live, "data-sx-activated", "true");
+ return domReplaceChild(domParent(dead), live, dead);
+})() : NIL); }, scripts);
+})() : NIL); };
// process-oob-swaps
- var processOobSwaps = function(container, swapFn) { return forEach(function(attr) { return (function() {
- var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]")));
+ var processOobSwaps = function(container, swapFn) { return (function() {
+ var oobs = findOobSwaps(container);
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 targetId = get(oob, "target-id");
var target = domQueryById(targetId);
- return (isSxTruthy(target) ? swapFn(target, oob, swapType) : NIL);
-})() : NIL);
-})(); }, oobEls);
-})(); }, ["sx-swap-oob", "hx-swap-oob"]); };
+ var oobEl = get(oob, "element");
+ var swapType = get(oob, "swap-type");
+ if (isSxTruthy(domParent(oobEl))) {
+ domRemoveChild(domParent(oobEl), oobEl);
+}
+ return (isSxTruthy(target) ? swapFn(target, oobEl, swapType) : NIL);
+})(); }, oobs);
+})(); };
// 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);
-})(); };
+ var hoistHeadElements = function(container) { return forEach(function(style) { return (isSxTruthy(domParent(style)) ? domRemoveChild(domParent(style), style) : NIL); }, domQueryAll(container, "style[data-sx-css]")); };
// 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);
-})(); };
+ var processBoosted = function(root) { return forEach(function(container) { return boostDescendants(container); }, domQueryAll(sxOr(root, domBody()), "[sx-boost]")); };
// 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);
-})()); };
+ var boostDescendants = function(container) { return forEach(function(link) { return (isSxTruthy((isSxTruthy(!isProcessed(link, "boost")) && shouldBoostLink(link))) ? (markProcessed(link, "boost"), (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-push-url")) ? domSetAttr(link, "sx-push-url", "true") : NIL), bindBoostLink(link, domGetAttr(link, "href"))) : NIL); }, domQueryAll(container, "a[href]")); };
// 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);
-})(); };
+ var processSse = function(root) { return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), bindSse(el)) : NIL); }, domQueryAll(sxOr(root, domBody()), "[sx-sse]")); };
// bind-sse
- var bindSse = function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), (function() {
+ var bindSse = function(el) { return (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);
-})();
+ var eventName = parseSseSwap(el);
+ return eventSourceListen(source, eventName, function(data) { return bindSseSwap(el, data); });
})() : 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});
-})(); });
+ var bindSseSwap = function(el, data) { return (function() {
+ var target = resolveTarget(el);
+ var swapSpec = parseSwapSpec(domGetAttr(el, "sx-swap"), domHasClass(domBody(), "sx-transitions"));
+ var swapStyle = get(swapSpec, "style");
+ var useTransition = get(swapSpec, "transition");
+ var trimmed = trim(data);
+ return (isSxTruthy(!isEmpty(trimmed)) ? (isSxTruthy(startsWith(trimmed, "(")) ? (function() {
+ var rendered = sxRender(trimmed);
+ var container = domCreateElement("div", NIL);
+ domAppend(container, rendered);
+ return withTransition(useTransition, function() { return swapDomNodes(target, childrenToFragment(container), swapStyle); });
+})() : withTransition(useTransition, function() { return swapHtmlString(target, trimmed, swapStyle); })) : NIL);
})(); };
// 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 bindInlineHandlers = function(root) { return forEach(function(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); };
+ var body = nth(attr, 1);
+ return (isSxTruthy(startsWith(name, "sx-on:")) ? (function() {
+ var eventName = slice(name, 6);
+ return (isSxTruthy(!isProcessed(el, (String("on:") + String(eventName)))) ? (markProcessed(el, (String("on:") + String(eventName))), bindInlineHandler(el, eventName, body)) : NIL);
+})() : NIL);
+})(); }, domAttrList(el)); }, domQueryAll(sxOr(root, domBody()), "[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:load]")); };
// 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);
+ var bindPreloadFor = function(el) { return (function() {
+ var preloadAttr = domGetAttr(el, "sx-preload");
+ return (isSxTruthy(preloadAttr) ? (function() {
+ var info = getVerbInfo(el);
+ return (isSxTruthy(info) ? (function() {
+ var url = get(info, "url");
+ var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
+ var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]);
+ var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100);
+ return bindPreload(el, events, debounceMs, function() { return doPreload(url, headers); });
+})() : NIL);
})() : NIL);
-})(); });
-})() : NIL); };
-
- // do-preload
- var doPreload = function(url) { return (function() {
- var headers = buildRequestHeaders(NIL, loadedComponentNames(), _cssHash);
- return fetchPreload(url, headers, _preloadCache);
})(); };
+ // do-preload
+ var doPreload = function(url, headers) { return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? fetchPreload(url, headers, _preloadCache) : NIL); };
+
// VERB_SELECTOR
- var VERB_SELECTOR = "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]";
+ var VERB_SELECTOR = (String("[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);
+ var els = domQueryAll(sxOr(root, domBody()), VERB_SELECTOR);
+ return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "verb")) ? (markProcessed(el, "verb"), processOne(el)) : NIL); }, els);
})(); };
// 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 processOne = function(el) { return (function() {
var verbInfo = getVerbInfo(el);
- return (isSxTruthy(verbInfo) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL);
-})()) : NIL) : NIL); };
+ return (isSxTruthy(verbInfo) ? (isSxTruthy(!domHasAttr(el, "sx-disable")) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : 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";
+ var url = browserLocationHref();
+ return (isSxTruthy(main) ? (function() {
+ var headers = buildRequestHeaders(main, loadedComponentNames(), _cssHash);
return fetchAndRestore(main, url, headers, scrollY);
-})());
+})() : NIL);
})(); };
// engine-init
@@ -1756,11 +1720,9 @@
// =========================================================================
- // Platform interface — Engine (browser-only)
+ // Platform interface — Engine pure logic (browser + node compatible)
// =========================================================================
- // --- Browser/Network ---
-
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
@@ -1784,6 +1746,23 @@
}
}
+ function nowMs() { return Date.now(); }
+
+ function parseHeaderValue(s) {
+ if (!s) return null;
+ try {
+ if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
+ return JSON.parse(s);
+ } catch (e) { return null; }
+ }
+
+
+ // =========================================================================
+ // Platform interface — Orchestration (browser-only)
+ // =========================================================================
+
+ // --- Browser/Network ---
+
function browserNavigate(url) {
if (typeof location !== "undefined") location.assign(url);
}
@@ -1812,16 +1791,6 @@
return r === null ? NIL : r;
}
- function nowMs() { return Date.now(); }
-
- function parseHeaderValue(s) {
- if (!s) return null;
- try {
- if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
- return JSON.parse(s);
- } catch (e) { return null; }
- }
-
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
@@ -2504,7 +2473,7 @@
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)"
+ _version: "ref-2.0 (dom+engine+orchestration, bootstrap-compiled)"
};
diff --git a/shared/static/scripts/sx-ref.js b/shared/static/scripts/sx-ref.js
index 76c06bf..e7dc6ea 100644
--- a/shared/static/scripts/sx-ref.js
+++ b/shared/static/scripts/sx-ref.js
@@ -1400,6 +1400,9 @@
// parse-sse-swap
var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); };
+
+ // === Transpiled from orchestration ===
+
// _preload-cache
var _preloadCache = {};
@@ -1409,15 +1412,15 @@
// 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);
+ return (isSxTruthy(parsed) ? forEach(function(key) { return domDispatch(el, key, get(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() {
+ var trimmed = trim(name);
+ return (isSxTruthy(!isEmpty(trimmed)) ? domDispatch(el, trimmed, {}) : NIL);
})(); }, split(headerVal, ",")));
})() : NIL); };
// init-css-tracking
var initCssTracking = function() { return (function() {
- var meta = domQuery("meta[name=\"sx-css-classes\"]");
+ var meta = domQuery("meta[name=\"sx-css-hash\"]");
return (isSxTruthy(meta) ? (function() {
var content = domGetAttr(meta, "content");
return (isSxTruthy(content) ? (_cssHash = content) : NIL);
@@ -1426,65 +1429,58 @@
// 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");
-}
+ var info = sxOr(verbInfo, getVerbInfo(el));
+ return (isSxTruthy(isNil(info)) ? promiseResolve(NIL) : (function() {
+ var verb = get(info, "method");
+ var url = get(info, "url");
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));
+ var confirmMsg = domGetAttr(el, "sx-confirm");
+ return (isSxTruthy(confirmMsg) && !browserConfirm(confirmMsg));
})()) ? 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));
+ var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL);
+ return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!validateForRequest(el)) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams))));
})()));
+})());
})(); };
// 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")))) {
+ var sync = domGetAttr(el, "sx-sync");
+ if (isSxTruthy((sync == "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 body = get(bodyInfo, "body");
var ct = get(bodyInfo, "content-type");
+ var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
+ var csrf = csrfToken();
+ if (isSxTruthy(extraParams)) {
+ { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } }
+}
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() {
+ if (isSxTruthy(csrf)) {
+ headers["X-CSRFToken"] = csrf;
+}
+ return (function() {
+ var cached = preloadCacheGet(_preloadCache, finalUrl);
+ var optimisticState = applyOptimistic(el);
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)); });
-})()));
-})();
+ domAddClass(el, "sx-request");
+ domSetAttr(el, "aria-busy", "true");
+ domDispatch(el, "sx:beforeRequest", {["url"]: finalUrl, ["method"]: method});
+ return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["cross-origin"]: isCrossOrigin(finalUrl), ["preloaded"]: cached}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!respOk) ? (domDispatch(el, "sx:responseError", {["status"]: status, ["text"]: text}), handleRetry(el, verb, method, finalUrl, extraParams)) : (domDispatch(el, "sx:afterRequest", {["status"]: status}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isAbortError(err)) ? domDispatch(el, "sx:requestError", {["error"]: err}) : NIL)); });
})();
})();
})();
@@ -1492,257 +1488,225 @@
// 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"))); });
+ var respHeaders = processResponseHeaders(getHeader);
+ (function() {
+ var newHash = get(respHeaders, "css-hash");
+ return (isSxTruthy(newHash) ? (_cssHash = newHash) : NIL);
})();
+ dispatchTriggerEvents(el, get(respHeaders, "trigger"));
+ return (isSxTruthy(get(respHeaders, "redirect")) ? browserNavigate(get(respHeaders, "redirect")) : (isSxTruthy(get(respHeaders, "refresh")) ? browserReload() : (isSxTruthy(get(respHeaders, "location")) ? fetchLocation(get(respHeaders, "location")) : (function() {
+ var targetEl = (isSxTruthy(get(respHeaders, "retarget")) ? domQuery(get(respHeaders, "retarget")) : resolveTarget(el));
+ var swapSpec = parseSwapSpec(sxOr(get(respHeaders, "reswap"), domGetAttr(el, "sx-swap")), domHasClass(domBody(), "sx-transitions"));
+ var swapStyle = get(swapSpec, "style");
+ var useTransition = get(swapSpec, "transition");
+ var ct = sxOr(get(respHeaders, "content-type"), "");
+ (isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, targetEl, text, swapStyle, useTransition) : handleHtmlResponse(el, targetEl, text, swapStyle, useTransition));
+ dispatchTriggerEvents(el, get(respHeaders, "trigger-swap"));
+ handleHistory(el, url, respHeaders);
+ if (isSxTruthy(get(respHeaders, "trigger-settle"))) {
+ setTimeout_(function() { return dispatchTriggerEvents(el, get(respHeaders, "trigger-settle")); }, 20);
+}
+ return domDispatch(el, "sx:afterSwap", {["target"]: targetEl, ["swap"]: swapStyle});
})())));
})(); };
// handle-sx-response
- var handleSxResponse = function(el, target, swap, selectSel, text) { return (function() {
+ var handleSxResponse = function(el, target, text, swapStyle, useTransition) { 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 final = extractResponseCss(cleaned);
+ return (function() {
+ var trimmed = trim(final);
+ return (isSxTruthy(!isEmpty(trimmed)) ? (function() {
+ var rendered = sxRender(trimmed);
var container = domCreateElement("div", NIL);
- domAppend(container, dom);
+ domAppend(container, rendered);
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);
+ var selectSel = domGetAttr(el, "sx-select");
+ var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container));
+ return withTransition(useTransition, function() { return swapDomNodes(target, content, swapStyle); });
})();
})() : NIL);
})();
+})();
})(); };
// handle-html-response
- var handleHtmlResponse = function(el, target, swap, selectSel, text) { return (function() {
+ var handleHtmlResponse = function(el, target, text, swapStyle, useTransition) { 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);
-})();
+ return (isSxTruthy(doc) ? (function() {
+ var selectSel = domGetAttr(el, "sx-select");
+ return (isSxTruthy(selectSel) ? (function() {
+ var html = selectHtmlFromDoc(doc, selectSel);
+ return withTransition(useTransition, function() { return swapHtmlString(target, html, swapStyle); });
+})() : (function() {
+ var container = domCreateElement("div", NIL);
+ domSetInnerHtml(container, domBodyInnerHtml(doc));
+ processOobSwaps(container, function(t, oob, s) { return swapDomNodes(t, oob, s); });
+ hoistHeadElements(container);
+ return withTransition(useTransition, function() { return swapDomNodes(target, childrenToFragment(container), swapStyle); });
+})());
+})() : NIL);
})(); };
// handle-retry
- var handleRetry = function(el, verbInfo, extraParams) { return (function() {
+ var handleRetry = function(el, verb, method, url, 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);
+ return (isSxTruthy(spec) ? (function() {
+ var currentMs = sxOr(domGetAttr(el, "data-sx-retry-ms"), get(spec, "start-ms"));
+ return (function() {
+ var ms = parseInt_(currentMs, get(spec, "start-ms"));
+ domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(ms, get(spec, "cap-ms")))));
+ return setTimeout_(function() { return doFetch(el, verb, method, url, extraParams); }, ms);
+})();
})() : 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)))));
+ var triggers = sxOr(parseTriggerSpec(domGetAttr(el, "sx-trigger")), defaultTrigger(domTagName(el)));
+ return forEach(function(trigger) { return (function() {
+ var kind = classifyTrigger(trigger);
+ var mods = get(trigger, "modifiers");
+ return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "interval")) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, false, get(mods, "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, NIL, NIL); }, sxOr(get(mods, "delay"), 0)) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, true, get(mods, "delay")) : (isSxTruthy((kind == "event")) ? bindEvent(el, get(trigger, "event"), mods, verbInfo) : NIL)))));
})(); }, 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 bindEvent = function(el, eventName, mods, verbInfo) { return (function() {
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")});
+ var listenTarget = (isSxTruthy(get(mods, "from")) ? domQuery(get(mods, "from")) : el);
+ return (isSxTruthy(listenTarget) ? domAddListener(listenTarget, eventName, function(e) { return (function() {
+ var shouldFire = true;
+ if (isSxTruthy(get(mods, "changed"))) {
+ (function() {
+ var val = elementValue(el);
+ return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
+})();
+}
+ return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL))) : NIL);
+})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
})(); };
// post-swap
- var postSwap = function(root) { return (activateScripts(root), sxProcessScripts(root), sxHydrate(root), processElements(root)); };
+ var postSwap = function(root) { return activateScripts(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);
-})(); };
+ var activateScripts = function(root) { return (isSxTruthy(root) ? (function() {
+ var scripts = domQueryAll(root, "script");
+ return forEach(function(dead) { return (isSxTruthy((isSxTruthy(!domHasAttr(dead, "data-components")) && !domHasAttr(dead, "data-sx-activated"))) ? (function() {
+ var live = createScriptClone(dead);
+ domSetAttr(live, "data-sx-activated", "true");
+ return domReplaceChild(domParent(dead), live, dead);
+})() : NIL); }, scripts);
+})() : NIL); };
// process-oob-swaps
- var processOobSwaps = function(container, swapFn) { return forEach(function(attr) { return (function() {
- var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]")));
+ var processOobSwaps = function(container, swapFn) { return (function() {
+ var oobs = findOobSwaps(container);
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 targetId = get(oob, "target-id");
var target = domQueryById(targetId);
- return (isSxTruthy(target) ? swapFn(target, oob, swapType) : NIL);
-})() : NIL);
-})(); }, oobEls);
-})(); }, ["sx-swap-oob", "hx-swap-oob"]); };
+ var oobEl = get(oob, "element");
+ var swapType = get(oob, "swap-type");
+ if (isSxTruthy(domParent(oobEl))) {
+ domRemoveChild(domParent(oobEl), oobEl);
+}
+ return (isSxTruthy(target) ? swapFn(target, oobEl, swapType) : NIL);
+})(); }, oobs);
+})(); };
// 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);
-})(); };
+ var hoistHeadElements = function(container) { return forEach(function(style) { return (isSxTruthy(domParent(style)) ? domRemoveChild(domParent(style), style) : NIL); }, domQueryAll(container, "style[data-sx-css]")); };
// 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);
-})(); };
+ var processBoosted = function(root) { return forEach(function(container) { return boostDescendants(container); }, domQueryAll(sxOr(root, domBody()), "[sx-boost]")); };
// 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);
-})()); };
+ var boostDescendants = function(container) { return forEach(function(link) { return (isSxTruthy((isSxTruthy(!isProcessed(link, "boost")) && shouldBoostLink(link))) ? (markProcessed(link, "boost"), (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-push-url")) ? domSetAttr(link, "sx-push-url", "true") : NIL), bindBoostLink(link, domGetAttr(link, "href"))) : NIL); }, domQueryAll(container, "a[href]")); };
// 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);
-})(); };
+ var processSse = function(root) { return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), bindSse(el)) : NIL); }, domQueryAll(sxOr(root, domBody()), "[sx-sse]")); };
// bind-sse
- var bindSse = function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), (function() {
+ var bindSse = function(el) { return (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);
-})();
+ var eventName = parseSseSwap(el);
+ return eventSourceListen(source, eventName, function(data) { return bindSseSwap(el, data); });
})() : 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});
-})(); });
+ var bindSseSwap = function(el, data) { return (function() {
+ var target = resolveTarget(el);
+ var swapSpec = parseSwapSpec(domGetAttr(el, "sx-swap"), domHasClass(domBody(), "sx-transitions"));
+ var swapStyle = get(swapSpec, "style");
+ var useTransition = get(swapSpec, "transition");
+ var trimmed = trim(data);
+ return (isSxTruthy(!isEmpty(trimmed)) ? (isSxTruthy(startsWith(trimmed, "(")) ? (function() {
+ var rendered = sxRender(trimmed);
+ var container = domCreateElement("div", NIL);
+ domAppend(container, rendered);
+ return withTransition(useTransition, function() { return swapDomNodes(target, childrenToFragment(container), swapStyle); });
+})() : withTransition(useTransition, function() { return swapHtmlString(target, trimmed, swapStyle); })) : NIL);
})(); };
// 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 bindInlineHandlers = function(root) { return forEach(function(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); };
+ var body = nth(attr, 1);
+ return (isSxTruthy(startsWith(name, "sx-on:")) ? (function() {
+ var eventName = slice(name, 6);
+ return (isSxTruthy(!isProcessed(el, (String("on:") + String(eventName)))) ? (markProcessed(el, (String("on:") + String(eventName))), bindInlineHandler(el, eventName, body)) : NIL);
+})() : NIL);
+})(); }, domAttrList(el)); }, domQueryAll(sxOr(root, domBody()), "[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:load]")); };
// 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);
+ var bindPreloadFor = function(el) { return (function() {
+ var preloadAttr = domGetAttr(el, "sx-preload");
+ return (isSxTruthy(preloadAttr) ? (function() {
+ var info = getVerbInfo(el);
+ return (isSxTruthy(info) ? (function() {
+ var url = get(info, "url");
+ var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
+ var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]);
+ var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100);
+ return bindPreload(el, events, debounceMs, function() { return doPreload(url, headers); });
+})() : NIL);
})() : NIL);
-})(); });
-})() : NIL); };
-
- // do-preload
- var doPreload = function(url) { return (function() {
- var headers = buildRequestHeaders(NIL, loadedComponentNames(), _cssHash);
- return fetchPreload(url, headers, _preloadCache);
})(); };
+ // do-preload
+ var doPreload = function(url, headers) { return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? fetchPreload(url, headers, _preloadCache) : NIL); };
+
// VERB_SELECTOR
- var VERB_SELECTOR = "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]";
+ var VERB_SELECTOR = (String("[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);
+ var els = domQueryAll(sxOr(root, domBody()), VERB_SELECTOR);
+ return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "verb")) ? (markProcessed(el, "verb"), processOne(el)) : NIL); }, els);
})(); };
// 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 processOne = function(el) { return (function() {
var verbInfo = getVerbInfo(el);
- return (isSxTruthy(verbInfo) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL);
-})()) : NIL) : NIL); };
+ return (isSxTruthy(verbInfo) ? (isSxTruthy(!domHasAttr(el, "sx-disable")) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : 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";
+ var url = browserLocationHref();
+ return (isSxTruthy(main) ? (function() {
+ var headers = buildRequestHeaders(main, loadedComponentNames(), _cssHash);
return fetchAndRestore(main, url, headers, scrollY);
-})());
+})() : NIL);
})(); };
// engine-init
@@ -1904,11 +1868,9 @@
// =========================================================================
- // Platform interface — Engine (browser-only)
+ // Platform interface — Engine pure logic (browser + node compatible)
// =========================================================================
- // --- Browser/Network ---
-
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
@@ -1932,6 +1894,23 @@
}
}
+ function nowMs() { return Date.now(); }
+
+ function parseHeaderValue(s) {
+ if (!s) return null;
+ try {
+ if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
+ return JSON.parse(s);
+ } catch (e) { return null; }
+ }
+
+
+ // =========================================================================
+ // Platform interface — Orchestration (browser-only)
+ // =========================================================================
+
+ // --- Browser/Network ---
+
function browserNavigate(url) {
if (typeof location !== "undefined") location.assign(url);
}
@@ -1960,16 +1939,6 @@
return r === null ? NIL : r;
}
- function nowMs() { return Date.now(); }
-
- function parseHeaderValue(s) {
- if (!s) return null;
- try {
- if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
- return JSON.parse(s);
- } catch (e) { return null; }
- }
-
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
@@ -2670,7 +2639,7 @@
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)"
+ _version: "ref-2.0 (dom+engine+html+orchestration+sx, bootstrap-compiled)"
};
diff --git a/shared/sx/ref/bootstrap_js.py b/shared/sx/ref/bootstrap_js.py
index 473375b..16bb415 100644
--- a/shared/sx/ref/bootstrap_js.py
+++ b/shared/sx/ref/bootstrap_js.py
@@ -705,14 +705,15 @@ def extract_defines(source: str) -> list[tuple[str, list]]:
ADAPTER_FILES = {
- "html": ("adapter-html.sx", "adapter-html"),
- "sx": ("adapter-sx.sx", "adapter-sx"),
- "dom": ("adapter-dom.sx", "adapter-dom"),
- "engine": ("engine.sx", "engine"),
+ "html": ("adapter-html.sx", "adapter-html"),
+ "sx": ("adapter-sx.sx", "adapter-sx"),
+ "dom": ("adapter-dom.sx", "adapter-dom"),
+ "engine": ("engine.sx", "engine"),
+ "orchestration": ("orchestration.sx","orchestration"),
}
-# Dependencies: engine requires dom
-ADAPTER_DEPS = {"engine": ["dom"]}
+# Dependencies: orchestration requires engine+dom, engine requires dom
+ADAPTER_DEPS = {"engine": ["dom"], "orchestration": ["engine", "dom"]}
def compile_ref_to_js(adapters: list[str] | None = None) -> str:
@@ -728,8 +729,9 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str:
# Platform JS blocks keyed by adapter name
adapter_platform = {
- "dom": PLATFORM_DOM_JS,
- "engine": PLATFORM_ENGINE_JS,
+ "dom": PLATFORM_DOM_JS,
+ "engine": PLATFORM_ENGINE_PURE_JS,
+ "orchestration": PLATFORM_ORCHESTRATION_JS,
}
# Resolve adapter set
@@ -750,7 +752,7 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str:
("eval.sx", "eval"),
("render.sx", "render (core)"),
]
- for name in ("html", "sx", "dom", "engine"):
+ for name in ("html", "sx", "dom", "engine", "orchestration"):
if name in adapter_set:
sx_files.append(ADAPTER_FILES[name])
@@ -769,6 +771,7 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str:
has_sx = "sx" in adapter_set
has_dom = "dom" in adapter_set
has_engine = "engine" in adapter_set
+ has_orch = "orchestration" in adapter_set
adapter_label = "+".join(sorted(adapter_set)) if adapter_set else "core-only"
parts = []
@@ -784,12 +787,12 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str:
# Platform JS for selected adapters
if not has_dom:
parts.append("\n var _hasDom = false;\n")
- for name in ("dom", "engine"):
+ for name in ("dom", "engine", "orchestration"):
if name in adapter_set and name in adapter_platform:
parts.append(adapter_platform[name])
parts.append(fixups_js(has_html, has_sx, has_dom))
- parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, adapter_label))
+ parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, adapter_label))
parts.append(EPILOGUE)
return "\n".join(parts)
@@ -1441,13 +1444,11 @@ PLATFORM_DOM_JS = """
function domTagName(el) { return el && el.tagName ? el.tagName : ""; }
"""
-PLATFORM_ENGINE_JS = """
+PLATFORM_ENGINE_PURE_JS = """
// =========================================================================
- // Platform interface — Engine (browser-only)
+ // Platform interface — Engine pure logic (browser + node compatible)
// =========================================================================
- // --- Browser/Network ---
-
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
@@ -1471,6 +1472,24 @@ PLATFORM_ENGINE_JS = """
}
}
+ function nowMs() { return Date.now(); }
+
+ function parseHeaderValue(s) {
+ if (!s) return null;
+ try {
+ if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
+ return JSON.parse(s);
+ } catch (e) { return null; }
+ }
+"""
+
+PLATFORM_ORCHESTRATION_JS = """
+ // =========================================================================
+ // Platform interface — Orchestration (browser-only)
+ // =========================================================================
+
+ // --- Browser/Network ---
+
function browserNavigate(url) {
if (typeof location !== "undefined") location.assign(url);
}
@@ -1499,16 +1518,6 @@ PLATFORM_ENGINE_JS = """
return r === null ? NIL : r;
}
- function nowMs() { return Date.now(); }
-
- function parseHeaderValue(s) {
- if (!s) return null;
- try {
- if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
- return JSON.parse(s);
- } catch (e) { return null; }
- }
-
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
@@ -2059,7 +2068,7 @@ def fixups_js(has_html, has_sx, has_dom):
return "\n".join(lines)
-def public_api_js(has_html, has_sx, has_dom, has_engine, adapter_label):
+def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, adapter_label):
# Parser is always included
parser = r'''
// =========================================================================
@@ -2255,6 +2264,7 @@ 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,')
+ if has_orch:
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,')
@@ -2263,7 +2273,7 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, adapter_label):
api_lines.append(f' _version: "{version}"')
api_lines.append(' };')
api_lines.append('')
- if has_engine:
+ if has_orch:
api_lines.append('''
// --- Popstate listener ---
if (typeof window !== "undefined") {
diff --git a/shared/sx/ref/engine.sx b/shared/sx/ref/engine.sx
index 8e84d9f..1ab88e3 100644
--- a/shared/sx/ref/engine.sx
+++ b/shared/sx/ref/engine.sx
@@ -1,41 +1,15 @@
;; ==========================================================================
-;; engine.sx — SxEngine specification
+;; engine.sx — SxEngine pure logic
;;
;; Fetch/swap/history engine for browser-side SX. Like HTMX but native
;; to the SX rendering pipeline.
;;
-;; This file specifies the LOGIC of the engine in s-expressions.
-;; Browser-specific APIs (fetch, DOM, history, events) are declared as
-;; platform interface at the bottom.
+;; This file specifies the pure LOGIC of the engine in s-expressions:
+;; parsing trigger specs, morph algorithm, swap dispatch, header building,
+;; retry logic, target resolution, etc.
;;
-;; The engine processes elements with sx-* attributes:
-;; sx-get, sx-post, sx-put, sx-delete, sx-patch — HTTP verb + URL
-;; sx-trigger — when to fire (click, submit, change, every 5s, ...)
-;; sx-target — where to swap response (#selector, "this", "closest")
-;; sx-swap — how to swap (innerHTML, outerHTML, afterend, ...)
-;; sx-select — filter response (CSS selector)
-;; sx-confirm — confirmation dialog before request
-;; sx-prompt — prompt dialog, sends result as SX-Prompt header
-;; sx-validate — form validation before request
-;; sx-encoding — "json" for JSON body instead of form-encoded
-;; sx-params — filter form fields (include, exclude, none)
-;; sx-include — include extra inputs from other elements
-;; sx-vals — extra key-value pairs to send
-;; sx-headers — extra request headers
-;; sx-indicator — show/hide loading indicator
-;; sx-disabled-elt — disable elements during request
-;; sx-push-url — push to browser history
-;; sx-replace-url — replace browser history
-;; sx-sync — abort previous request ("replace")
-;; sx-media — only fire if media query matches
-;; sx-preload — preload on mousedown/mouseover
-;; sx-boost — auto-boost links and forms in container
-;; sx-sse — connect to Server-Sent Events
-;; sx-retry — retry on failure (exponential:startMs:capMs)
-;; sx-optimistic — optimistic update (remove, disable, add-class:name)
-;; sx-preserve — don't morph this element during swap
-;; sx-ignore — skip morphing entirely
-;; sx-on:* — inline event handlers (beforeRequest, afterSwap, ...)
+;; Orchestration (binding events, executing requests, processing elements)
+;; lives in orchestration.sx, which depends on this file.
;;
;; Depends on:
;; adapter-dom.sx — render-to-dom (for SX response rendering)
@@ -662,834 +636,29 @@
(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
- (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