Web extension module for def-forms + modifier-key clicks + CSSX SSR fix
Move defhandler/defquery/defaction/defpage/defrelation from hardcoded evaluator dispatch to web/web-forms.sx extension module, registered via register-special-form!. Adapters updated to use definition-form? and dynamically extended form-name lists. Fix modifier-key clicks (ctrl-click → new tab) in three click handlers: bindBoostLink, bindClientRouteClick, and orchestration.sx bind-event. Add event-modifier-key? primitive (eventModifierKey_p for transpiler). Fix CSSX SSR: ~cssx/flush no longer drains the collected bucket on the server, so the shell template correctly emits CSSX rules in <head>. Add missing server-side DOM stubs (create-text-node, dom-append, etc.) and SSR passthrough for portal/error-boundary/promise-delayed. Passive event listeners for touch/wheel/scroll to fix touchpad scrolling. 97/97 Playwright demo tests + 4/4 isomorphic SSR tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-24T04:23:51Z";
|
||||
var SX_VERSION = "2026-03-24T09:53:22Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -2237,7 +2237,7 @@ PRIMITIVES["VOID_ELEMENTS"] = VOID_ELEMENTS;
|
||||
PRIMITIVES["BOOLEAN_ATTRS"] = BOOLEAN_ATTRS;
|
||||
|
||||
// definition-form?
|
||||
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "deftype"), (name == "defeffect")); };
|
||||
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "deftype"), (name == "defeffect")); };
|
||||
PRIMITIVES["definition-form?"] = isDefinitionForm;
|
||||
|
||||
// parse-element-args
|
||||
@@ -2520,7 +2520,7 @@ PRIMITIVES["render-to-html"] = renderToHtml;
|
||||
PRIMITIVES["render-value-to-html"] = renderValueToHtml;
|
||||
|
||||
// RENDER_HTML_FORMS
|
||||
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "letrec", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each", "scope", "provide"];
|
||||
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "letrec", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each", "scope", "provide"];
|
||||
PRIMITIVES["RENDER_HTML_FORMS"] = RENDER_HTML_FORMS;
|
||||
|
||||
// render-html-form?
|
||||
@@ -2533,10 +2533,10 @@ PRIMITIVES["render-html-form?"] = isRenderHtmlForm;
|
||||
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() {
|
||||
var name = symbolName(head);
|
||||
var args = rest(expr);
|
||||
return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||
return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(sxOr((name == "portal"), (name == "error-boundary"), (name == "promise-delayed"))) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||
var val = envGet(env, name);
|
||||
return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name)))));
|
||||
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))));
|
||||
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))));
|
||||
})());
|
||||
})()); };
|
||||
PRIMITIVES["render-list-to-html"] = renderListToHtml;
|
||||
@@ -3110,7 +3110,7 @@ PRIMITIVES["render-dom-raw"] = renderDomRaw;
|
||||
PRIMITIVES["render-dom-unknown-component"] = renderDomUnknownComponent;
|
||||
|
||||
// RENDER_DOM_FORMS
|
||||
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "scope", "provide"];
|
||||
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "scope", "provide"];
|
||||
PRIMITIVES["RENDER_DOM_FORMS"] = RENDER_DOM_FORMS;
|
||||
|
||||
// render-dom-form?
|
||||
@@ -4181,7 +4181,7 @@ PRIMITIVES["bind-triggers"] = bindTriggers;
|
||||
return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
|
||||
})();
|
||||
}
|
||||
return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() {
|
||||
return (isSxTruthy(shouldFire) ? ((isSxTruthy((isSxTruthy((eventName == "click")) && eventModifierKey_p(e))) ? (shouldFire = false) : NIL), (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() {
|
||||
var liveInfo = sxOr(getVerbInfo(el), verbInfo);
|
||||
var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay")));
|
||||
var clientRouted = false;
|
||||
@@ -4189,7 +4189,7 @@ PRIMITIVES["bind-triggers"] = bindTriggers;
|
||||
clientRouted = tryClientRoute(urlPathname(get(liveInfo, "url")), domGetAttr(el, "sx-target"));
|
||||
}
|
||||
return (isSxTruthy(clientRouted) ? (browserPushState(get(liveInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(liveInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "delay")))) : executeRequest(el, NIL, NIL))));
|
||||
})()) : NIL);
|
||||
})()) : NIL)) : NIL);
|
||||
})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
|
||||
})(); };
|
||||
PRIMITIVES["bind-event"] = bindEvent;
|
||||
@@ -5880,6 +5880,7 @@ PRIMITIVES["resource"] = resource;
|
||||
if (typeof domClosest === "function") PRIMITIVES["dom-closest"] = domClosest;
|
||||
if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches;
|
||||
if (typeof preventDefault_ === "function") PRIMITIVES["prevent-default"] = preventDefault_;
|
||||
if (typeof eventModifierKey_p === "function") PRIMITIVES["event-modifier-key?"] = eventModifierKey_p;
|
||||
if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue;
|
||||
if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml;
|
||||
if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml;
|
||||
@@ -6183,8 +6184,10 @@ PRIMITIVES["resource"] = resource;
|
||||
: function(e) { try { cekCall(handler, [e]); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } finally { runPostRenderHooks(); } })
|
||||
: handler;
|
||||
if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler));
|
||||
el.addEventListener(name, wrapped);
|
||||
return function() { el.removeEventListener(name, wrapped); };
|
||||
var passiveEvents = { touchstart: 1, touchmove: 1, wheel: 1, scroll: 1 };
|
||||
var opts = passiveEvents[name] ? { passive: true } : undefined;
|
||||
el.addEventListener(name, wrapped, opts);
|
||||
return function() { el.removeEventListener(name, wrapped, opts); };
|
||||
}
|
||||
|
||||
function eventDetail(e) {
|
||||
@@ -6802,6 +6805,7 @@ PRIMITIVES["resource"] = resource;
|
||||
|
||||
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
|
||||
function stopPropagation_(e) { if (e && e.stopPropagation) e.stopPropagation(); }
|
||||
function eventModifierKey_p(e) { return !!(e && (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey)); }
|
||||
function domFocus(el) { if (el && el.focus) el.focus(); }
|
||||
function tryCatch(tryFn, catchFn) {
|
||||
var t = _wrapSxFn(tryFn);
|
||||
@@ -6905,6 +6909,7 @@ PRIMITIVES["resource"] = resource;
|
||||
|
||||
function bindBoostLink(el, _href) {
|
||||
el.addEventListener("click", function(e) {
|
||||
if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
|
||||
e.preventDefault();
|
||||
// Re-read href from element at click time (not closed-over value)
|
||||
var liveHref = el.getAttribute("href") || _href;
|
||||
@@ -6936,6 +6941,7 @@ PRIMITIVES["resource"] = resource;
|
||||
|
||||
function bindClientRouteClick(link, _href, fallbackFn) {
|
||||
link.addEventListener("click", function(e) {
|
||||
if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
|
||||
e.preventDefault();
|
||||
// Re-read href from element at click time (not closed-over value)
|
||||
var liveHref = link.getAttribute("href") || _href;
|
||||
@@ -7086,7 +7092,7 @@ PRIMITIVES["resource"] = resource;
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
}, { passive: true });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user