From 9caf8b6e9467c324550e16ae38ffc799fa65928f Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 24 Mar 2026 12:10:54 +0000 Subject: [PATCH] Fix runtime PRIMITIVES for dom/browser library functions dom.sx and browser.sx are library source (not transpiled into the bundle), so their functions need explicit PRIMITIVES registration for runtime-eval'd SX code (islands, data-init scripts). Restore registrations for all dom/ browser functions used at runtime. Revert bootstrap.py transpilation of dom-lib/browser-lib which overrode native platform implementations that have essential runtime integration (cekCall wrapping, post-render hooks). Add Playwright regression test for [object Object] nav link issue. Replace console-log calls with log-info in init-client.sx.txt. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/javascript/bootstrap.py | 2 +- hosts/javascript/platform.py | 77 ++++- shared/static/scripts/sx-browser.js | 486 +++++----------------------- sx/sxc/init-client.sx.txt | 4 +- tests/playwright/isomorphic.spec.js | 18 ++ 5 files changed, 173 insertions(+), 414 deletions(-) diff --git a/hosts/javascript/bootstrap.py b/hosts/javascript/bootstrap.py index a145617..b7d1a95 100644 --- a/hosts/javascript/bootstrap.py +++ b/hosts/javascript/bootstrap.py @@ -137,7 +137,7 @@ def compile_ref_to_js( ("content.sx", "content (content-addressed computation)"), ("render.sx", "render (core)"), ] - for name in ("parser", "html", "sx", "dom-lib", "browser-lib", "dom", "engine", "orchestration", "boot"): + for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "boot"): if name in adapter_set: sx_files.append(ADAPTER_FILES[name]) # Use explicit ordering for spec modules (respects dependencies) diff --git a/hosts/javascript/platform.py b/hosts/javascript/platform.py index 463be70..fbb25b6 100644 --- a/hosts/javascript/platform.py +++ b/hosts/javascript/platform.py @@ -3210,7 +3210,82 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_ // ----------------------------------------------------------------------- // Core primitives that require native JS (cannot be expressed via FFI) // ----------------------------------------------------------------------- - PRIMITIVES["error"] = function(msg) { throw new Error(msg); };'''] + PRIMITIVES["error"] = function(msg) { throw new Error(msg); }; + + // FFI library functions — defined in dom.sx/browser.sx but not transpiled. + // Registered here so runtime-evaluated SX code (data-init, islands) can use them. + PRIMITIVES["prevent-default"] = preventDefault_; + PRIMITIVES["stop-propagation"] = stopPropagation_; + PRIMITIVES["event-modifier-key?"] = eventModifierKey_p; + PRIMITIVES["element-value"] = elementValue; + PRIMITIVES["error-message"] = errorMessage; + PRIMITIVES["schedule-idle"] = scheduleIdle; + PRIMITIVES["console-log"] = function() { + var args = Array.prototype.slice.call(arguments); + console.log.apply(console, ["[sx]"].concat(args)); + return args.length > 0 ? args[0] : NIL; + }; + PRIMITIVES["set-cookie"] = function(name, value, days) { + var d = days || 365; + var expires = new Date(Date.now() + d * 864e5).toUTCString(); + document.cookie = name + "=" + encodeURIComponent(value) + ";expires=" + expires + ";path=/;SameSite=Lax"; + return NIL; + }; + PRIMITIVES["get-cookie"] = function(name) { + var m = document.cookie.match(new RegExp("(?:^|;\\\\s*)" + name + "=([^;]*)")); + return m ? decodeURIComponent(m[1]) : NIL; + }; + + // dom.sx / browser.sx library functions — not transpiled, registered from + // native platform implementations so runtime-eval'd SX code can use them. + if (typeof domBody === "function") PRIMITIVES["dom-body"] = domBody; + if (typeof domQuery === "function") PRIMITIVES["dom-query"] = domQuery; + if (typeof domQueryAll === "function") PRIMITIVES["dom-query-all"] = domQueryAll; + if (typeof domQueryById === "function") PRIMITIVES["dom-query-by-id"] = domQueryById; + if (typeof domSetAttr === "function") PRIMITIVES["dom-set-attr"] = domSetAttr; + if (typeof domGetAttr === "function") PRIMITIVES["dom-get-attr"] = domGetAttr; + if (typeof domRemoveAttr === "function") PRIMITIVES["dom-remove-attr"] = domRemoveAttr; + if (typeof domHasAttr === "function") PRIMITIVES["dom-has-attr?"] = domHasAttr; + if (typeof domAddClass === "function") PRIMITIVES["dom-add-class"] = domAddClass; + if (typeof domRemoveClass === "function") PRIMITIVES["dom-remove-class"] = domRemoveClass; + if (typeof domHasClass === "function") PRIMITIVES["dom-has-class?"] = domHasClass; + if (typeof domClosest === "function") PRIMITIVES["dom-closest"] = domClosest; + if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches; + if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml; + if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml; + if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; + if (typeof domCreateElement === "function") PRIMITIVES["dom-create-element"] = domCreateElement; + if (typeof domAppend === "function") PRIMITIVES["dom-append"] = domAppend; + if (typeof domAppendToHead === "function") PRIMITIVES["dom-append-to-head"] = domAppendToHead; + if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; + if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; + PRIMITIVES["dom-listen"] = domListen; + PRIMITIVES["dom-dispatch"] = domDispatch; + PRIMITIVES["event-detail"] = eventDetail; + PRIMITIVES["create-text-node"] = createTextNode; + PRIMITIVES["dom-set-text-content"] = domSetTextContent; + PRIMITIVES["dom-focus"] = domFocus; + PRIMITIVES["dom-tag-name"] = domTagName; + PRIMITIVES["dom-get-prop"] = domGetProp; + PRIMITIVES["dom-set-prop"] = domSetProp; + PRIMITIVES["reactive-text"] = reactiveText; + PRIMITIVES["set-interval"] = setInterval_; + PRIMITIVES["clear-interval"] = clearInterval_; + PRIMITIVES["promise-then"] = promiseThen; + PRIMITIVES["promise-delayed"] = promiseDelayed; + PRIMITIVES["local-storage-get"] = function(key) { + try { var v = localStorage.getItem(key); return v === null ? NIL : v; } + catch (e) { return NIL; } + }; + PRIMITIVES["local-storage-set"] = function(key, val) { + try { localStorage.setItem(key, val); } catch (e) {} + return NIL; + }; + PRIMITIVES["local-storage-remove"] = function(key) { + try { localStorage.removeItem(key); } catch (e) {} + return NIL; + }; + if (typeof sxParse === "function") PRIMITIVES["sx-parse"] = sxParse;'''] if has_deps: lines.append(''' // Platform deps functions (native JS, not transpiled — need explicit registration) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 84f7b2b..98d18c3 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-24T11:42:17Z"; + var SX_VERSION = "2026-03-24T12:07:57Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -2969,415 +2969,6 @@ PRIMITIVES["aser-special"] = aserSpecial; PRIMITIVES["eval-case-aser"] = evalCaseAser; - // === Transpiled from lib/dom (DOM library) === - - // dom-document - var domDocument = function() { return hostGlobal("document"); }; -PRIMITIVES["dom-document"] = domDocument; - - // dom-window - var domWindow = function() { return hostGlobal("window"); }; -PRIMITIVES["dom-window"] = domWindow; - - // dom-body - var domBody = function() { return hostGet(domDocument(), "body"); }; -PRIMITIVES["dom-body"] = domBody; - - // dom-head - var domHead = function() { return hostGet(domDocument(), "head"); }; -PRIMITIVES["dom-head"] = domHead; - - // dom-create-element - var domCreateElement = function(tag) { return hostCall(domDocument(), "createElement", tag); }; -PRIMITIVES["dom-create-element"] = domCreateElement; - - // create-text-node - var createTextNode = function(s) { return hostCall(domDocument(), "createTextNode", s); }; -PRIMITIVES["create-text-node"] = createTextNode; - - // create-fragment - var createFragment = function() { return hostCall(domDocument(), "createDocumentFragment"); }; -PRIMITIVES["create-fragment"] = createFragment; - - // dom-append - var domAppend = function(parent, child) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "appendChild", child) : NIL); }; -PRIMITIVES["dom-append"] = domAppend; - - // dom-prepend - var domPrepend = function(parent, child) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "prepend", child) : NIL); }; -PRIMITIVES["dom-prepend"] = domPrepend; - - // dom-insert-before - var domInsertBefore = function(parent, child, ref) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "insertBefore", child, ref) : NIL); }; -PRIMITIVES["dom-insert-before"] = domInsertBefore; - - // dom-remove-child - var domRemoveChild = function(parent, child) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "removeChild", child) : NIL); }; -PRIMITIVES["dom-remove-child"] = domRemoveChild; - - // dom-replace-child - var domReplaceChild = function(parent, newChild, oldChild) { return (isSxTruthy((isSxTruthy(parent) && isSxTruthy(newChild) && oldChild)) ? hostCall(parent, "replaceChild", newChild, oldChild) : NIL); }; -PRIMITIVES["dom-replace-child"] = domReplaceChild; - - // dom-clone - var domClone = function(node, deep) { return hostCall(node, "cloneNode", (isSxTruthy(isNil(deep)) ? true : deep)); }; -PRIMITIVES["dom-clone"] = domClone; - - // dom-query - var domQuery = function(sel) { return hostCall(domDocument(), "querySelector", sel); }; -PRIMITIVES["dom-query"] = domQuery; - - // dom-query-all - var domQueryAll = function(root, sel) { return (isSxTruthy(isNil(sel)) ? hostCall(domDocument(), "querySelectorAll", root) : hostCall(root, "querySelectorAll", sel)); }; -PRIMITIVES["dom-query-all"] = domQueryAll; - - // dom-query-by-id - var domQueryById = function(id) { return hostCall(domDocument(), "getElementById", id); }; -PRIMITIVES["dom-query-by-id"] = domQueryById; - - // dom-closest - var domClosest = function(el, sel) { return (isSxTruthy(el) ? hostCall(el, "closest", sel) : NIL); }; -PRIMITIVES["dom-closest"] = domClosest; - - // dom-matches? - var domMatches = function(el, sel) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "matches"))) ? hostCall(el, "matches", sel) : false); }; -PRIMITIVES["dom-matches?"] = domMatches; - - // dom-get-attr - var domGetAttr = function(el, name) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "getAttribute"))) ? (function() { - var v = hostCall(el, "getAttribute", name); - return (isSxTruthy(isNil(v)) ? NIL : v); -})() : NIL); }; -PRIMITIVES["dom-get-attr"] = domGetAttr; - - // dom-set-attr - var domSetAttr = function(el, name, val) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "setAttribute"))) ? hostCall(el, "setAttribute", name, val) : NIL); }; -PRIMITIVES["dom-set-attr"] = domSetAttr; - - // dom-remove-attr - var domRemoveAttr = function(el, name) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "removeAttribute"))) ? hostCall(el, "removeAttribute", name) : NIL); }; -PRIMITIVES["dom-remove-attr"] = domRemoveAttr; - - // dom-has-attr? - var domHasAttr = function(el, name) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "hasAttribute"))) ? hostCall(el, "hasAttribute", name) : false); }; -PRIMITIVES["dom-has-attr?"] = domHasAttr; - - // dom-add-class - var domAddClass = function(el, cls) { return (isSxTruthy(el) ? hostCall(hostGet(el, "classList"), "add", cls) : NIL); }; -PRIMITIVES["dom-add-class"] = domAddClass; - - // dom-remove-class - var domRemoveClass = function(el, cls) { return (isSxTruthy(el) ? hostCall(hostGet(el, "classList"), "remove", cls) : NIL); }; -PRIMITIVES["dom-remove-class"] = domRemoveClass; - - // dom-has-class? - var domHasClass = function(el, cls) { return (isSxTruthy(el) ? hostCall(hostGet(el, "classList"), "contains", cls) : false); }; -PRIMITIVES["dom-has-class?"] = domHasClass; - - // dom-text-content - var domTextContent = function(el) { return hostGet(el, "textContent"); }; -PRIMITIVES["dom-text-content"] = domTextContent; - - // dom-set-text-content - var domSetTextContent = function(el, val) { return hostSet(el, "textContent", val); }; -PRIMITIVES["dom-set-text-content"] = domSetTextContent; - - // dom-inner-html - var domInnerHtml = function(el) { return hostGet(el, "innerHTML"); }; -PRIMITIVES["dom-inner-html"] = domInnerHtml; - - // dom-set-inner-html - var domSetInnerHtml = function(el, val) { return hostSet(el, "innerHTML", val); }; -PRIMITIVES["dom-set-inner-html"] = domSetInnerHtml; - - // dom-outer-html - var domOuterHtml = function(el) { return hostGet(el, "outerHTML"); }; -PRIMITIVES["dom-outer-html"] = domOuterHtml; - - // dom-insert-adjacent-html - var domInsertAdjacentHtml = function(el, position, html) { return hostCall(el, "insertAdjacentHTML", position, html); }; -PRIMITIVES["dom-insert-adjacent-html"] = domInsertAdjacentHtml; - - // dom-get-style - var domGetStyle = function(el, prop) { return hostGet(hostGet(el, "style"), prop); }; -PRIMITIVES["dom-get-style"] = domGetStyle; - - // dom-set-style - var domSetStyle = function(el, prop, val) { return hostCall(hostGet(el, "style"), "setProperty", prop, val); }; -PRIMITIVES["dom-set-style"] = domSetStyle; - - // dom-get-prop - var domGetProp = function(el, name) { return hostGet(el, name); }; -PRIMITIVES["dom-get-prop"] = domGetProp; - - // dom-set-prop - var domSetProp = function(el, name, val) { return hostSet(el, name, val); }; -PRIMITIVES["dom-set-prop"] = domSetProp; - - // dom-tag-name - var domTagName = function(el) { return (isSxTruthy(el) ? lower(sxOr(hostGet(el, "tagName"), "")) : ""); }; -PRIMITIVES["dom-tag-name"] = domTagName; - - // dom-node-type - var domNodeType = function(el) { return hostGet(el, "nodeType"); }; -PRIMITIVES["dom-node-type"] = domNodeType; - - // dom-node-name - var domNodeName = function(el) { return hostGet(el, "nodeName"); }; -PRIMITIVES["dom-node-name"] = domNodeName; - - // dom-id - var domId = function(el) { return hostGet(el, "id"); }; -PRIMITIVES["dom-id"] = domId; - - // dom-parent - var domParent = function(el) { return hostGet(el, "parentNode"); }; -PRIMITIVES["dom-parent"] = domParent; - - // dom-first-child - var domFirstChild = function(el) { return hostGet(el, "firstChild"); }; -PRIMITIVES["dom-first-child"] = domFirstChild; - - // dom-next-sibling - var domNextSibling = function(el) { return hostGet(el, "nextSibling"); }; -PRIMITIVES["dom-next-sibling"] = domNextSibling; - - // dom-child-list - var domChildList = function(el) { return (isSxTruthy(el) ? hostCall(hostGlobal("Array"), "from", hostGet(el, "childNodes")) : []); }; -PRIMITIVES["dom-child-list"] = domChildList; - - // dom-is-fragment? - var domIsFragment = function(el) { return (hostGet(el, "nodeType") == 11); }; -PRIMITIVES["dom-is-fragment?"] = domIsFragment; - - // dom-focus - var domFocus = function(el) { return (isSxTruthy(el) ? hostCall(el, "focus") : NIL); }; -PRIMITIVES["dom-focus"] = domFocus; - - // dom-parse-html - var domParseHtml = function(html) { return (function() { - var parser = hostNew("DOMParser"); - var doc = hostCall(parser, "parseFromString", html, "text/html"); - return hostGet(hostGet(doc, "body"), "childNodes"); -})(); }; -PRIMITIVES["dom-parse-html"] = domParseHtml; - - // dom-listen - var domListen = function(el, eventName, handler) { return (function() { - var cb = hostCallback(handler); - hostCall(el, "addEventListener", eventName, cb); - return function() { return hostCall(el, "removeEventListener", eventName, cb); }; -})(); }; -PRIMITIVES["dom-listen"] = domListen; - - // dom-dispatch - var domDispatch = function(el, eventName, detail) { return (function() { - var evt = hostNew("CustomEvent", eventName, {["detail"]: detail, ["bubbles"]: true}); - return hostCall(el, "dispatchEvent", evt); -})(); }; -PRIMITIVES["dom-dispatch"] = domDispatch; - - // event-detail - var eventDetail = function(evt) { return hostGet(evt, "detail"); }; -PRIMITIVES["event-detail"] = eventDetail; - - // prevent-default - var preventDefault_ = function(e) { return (isSxTruthy(e) ? hostCall(e, "preventDefault") : NIL); }; -PRIMITIVES["prevent-default"] = preventDefault_; - - // stop-propagation - var stopPropagation_ = function(e) { return (isSxTruthy(e) ? hostCall(e, "stopPropagation") : NIL); }; -PRIMITIVES["stop-propagation"] = stopPropagation_; - - // event-modifier-key? - var eventModifierKey_p = function(e) { return (isSxTruthy(e) && sxOr(hostGet(e, "ctrlKey"), hostGet(e, "metaKey"), hostGet(e, "shiftKey"), hostGet(e, "altKey"))); }; -PRIMITIVES["event-modifier-key?"] = eventModifierKey_p; - - // element-value - var elementValue = function(el) { return (isSxTruthy((isSxTruthy(el) && !isSxTruthy(isNil(hostGet(el, "value"))))) ? hostGet(el, "value") : NIL); }; -PRIMITIVES["element-value"] = elementValue; - - // error-message - var errorMessage = function(e) { return (isSxTruthy((isSxTruthy(e) && hostGet(e, "message"))) ? hostGet(e, "message") : (String(e))); }; -PRIMITIVES["error-message"] = errorMessage; - - // dom-get-data - var domGetData = function(el, key) { return (function() { - var store = hostGet(el, "__sx_data"); - return (isSxTruthy(store) ? hostGet(store, key) : NIL); -})(); }; -PRIMITIVES["dom-get-data"] = domGetData; - - // dom-set-data - var domSetData = function(el, key, val) { if (isSxTruthy(!isSxTruthy(hostGet(el, "__sx_data")))) { - hostSet(el, "__sx_data", {}); -} -return hostSet(hostGet(el, "__sx_data"), key, val); }; -PRIMITIVES["dom-set-data"] = domSetData; - - // dom-append-to-head - var domAppendToHead = function(el) { return (isSxTruthy(domHead()) ? hostCall(domHead(), "appendChild", el) : NIL); }; -PRIMITIVES["dom-append-to-head"] = domAppendToHead; - - // set-document-title - var setDocumentTitle = function(title) { return hostSet(domDocument(), "title", title); }; -PRIMITIVES["set-document-title"] = setDocumentTitle; - - - // === Transpiled from lib/browser (browser API library) === - - // browser-location-href - var browserLocationHref = function() { return hostGet(hostGet(domWindow(), "location"), "href"); }; -PRIMITIVES["browser-location-href"] = browserLocationHref; - - // browser-location-pathname - var browserLocationPathname = function() { return hostGet(hostGet(domWindow(), "location"), "pathname"); }; -PRIMITIVES["browser-location-pathname"] = browserLocationPathname; - - // browser-location-origin - var browserLocationOrigin = function() { return hostGet(hostGet(domWindow(), "location"), "origin"); }; -PRIMITIVES["browser-location-origin"] = browserLocationOrigin; - - // browser-same-origin? - var browserSameOrigin = function(url) { return startsWith(url, browserLocationOrigin()); }; -PRIMITIVES["browser-same-origin?"] = browserSameOrigin; - - // browser-push-state - var browserPushState = function(state, title, url) { return hostCall(hostGet(domWindow(), "history"), "pushState", state, title, url); }; -PRIMITIVES["browser-push-state"] = browserPushState; - - // browser-replace-state - var browserReplaceState = function(state, title, url) { return hostCall(hostGet(domWindow(), "history"), "replaceState", state, title, url); }; -PRIMITIVES["browser-replace-state"] = browserReplaceState; - - // browser-reload - var browserReload = function() { return hostCall(hostGet(domWindow(), "location"), "reload"); }; -PRIMITIVES["browser-reload"] = browserReload; - - // browser-navigate - var browserNavigate = function(url) { return hostSet(hostGet(domWindow(), "location"), "href", url); }; -PRIMITIVES["browser-navigate"] = browserNavigate; - - // local-storage-get - var localStorageGet = function(key) { return hostCall(hostGet(domWindow(), "localStorage"), "getItem", key); }; -PRIMITIVES["local-storage-get"] = localStorageGet; - - // local-storage-set - var localStorageSet = function(key, val) { return hostCall(hostGet(domWindow(), "localStorage"), "setItem", key, val); }; -PRIMITIVES["local-storage-set"] = localStorageSet; - - // local-storage-remove - var localStorageRemove = function(key) { return hostCall(hostGet(domWindow(), "localStorage"), "removeItem", key); }; -PRIMITIVES["local-storage-remove"] = localStorageRemove; - - // set-timeout - var setTimeout_ = function(fnVal, ms) { return hostCall(domWindow(), "setTimeout", hostCallback(fnVal), ms); }; -PRIMITIVES["set-timeout"] = setTimeout_; - - // set-interval - var setInterval_ = function(fnVal, ms) { return hostCall(domWindow(), "setInterval", hostCallback(fnVal), ms); }; -PRIMITIVES["set-interval"] = setInterval_; - - // clear-timeout - var clearTimeout_ = function(id) { return hostCall(domWindow(), "clearTimeout", id); }; -PRIMITIVES["clear-timeout"] = clearTimeout_; - - // clear-interval - var clearInterval_ = function(id) { return hostCall(domWindow(), "clearInterval", id); }; -PRIMITIVES["clear-interval"] = clearInterval_; - - // request-animation-frame - var requestAnimationFrame_ = function(fnVal) { return hostCall(domWindow(), "requestAnimationFrame", hostCallback(fnVal)); }; -PRIMITIVES["request-animation-frame"] = requestAnimationFrame_; - - // fetch-request - var fetchRequest = function(url, opts) { return hostCall(domWindow(), "fetch", url, opts); }; -PRIMITIVES["fetch-request"] = fetchRequest; - - // new-abort-controller - var newAbortController = function() { return hostNew("AbortController"); }; -PRIMITIVES["new-abort-controller"] = newAbortController; - - // controller-signal - var controllerSignal = function(controller) { return hostGet(controller, "signal"); }; -PRIMITIVES["controller-signal"] = controllerSignal; - - // controller-abort - var controllerAbort = function(controller) { return hostCall(controller, "abort"); }; -PRIMITIVES["controller-abort"] = controllerAbort; - - // promise-then - var promiseThen = function(p, onResolve, onReject) { return (function() { - var cbResolve = hostCallback(onResolve); - var cbReject = (isSxTruthy(onReject) ? hostCallback(onReject) : NIL); - return (isSxTruthy(cbReject) ? hostCall(hostCall(p, "then", cbResolve), "catch", cbReject) : hostCall(p, "then", cbResolve)); -})(); }; -PRIMITIVES["promise-then"] = promiseThen; - - // promise-resolve - var promiseResolve = function(val) { return hostCall(hostGlobal("Promise"), "resolve", val); }; -PRIMITIVES["promise-resolve"] = promiseResolve; - - // promise-delayed - var promiseDelayed = function(ms, val) { return hostNew("Promise", hostCallback(function(resolve) { return setTimeout_(function() { return hostCall(resolve, "call", NIL, val); }, ms); })); }; -PRIMITIVES["promise-delayed"] = promiseDelayed; - - // browser-confirm - var browserConfirm = function(msg) { return hostCall(domWindow(), "confirm", msg); }; -PRIMITIVES["browser-confirm"] = browserConfirm; - - // browser-prompt - var browserPrompt = function(msg, default_) { return hostCall(domWindow(), "prompt", msg, default_); }; -PRIMITIVES["browser-prompt"] = browserPrompt; - - // browser-media-matches? - var browserMediaMatches = function(query) { return hostGet(hostCall(domWindow(), "matchMedia", query), "matches"); }; -PRIMITIVES["browser-media-matches?"] = browserMediaMatches; - - // json-parse - var jsonParse = function(s) { return hostCall(hostGlobal("JSON"), "parse", s); }; -PRIMITIVES["json-parse"] = jsonParse; - - // log-info - var logInfo = function(msg) { return hostCall(hostGlobal("console"), "log", (String("[sx] ") + String(msg))); }; -PRIMITIVES["log-info"] = logInfo; - - // log-warn - var logWarn = function(msg) { return hostCall(hostGlobal("console"), "warn", (String("[sx] ") + String(msg))); }; -PRIMITIVES["log-warn"] = logWarn; - - // console-log - var consoleLog = function() { var args = Array.prototype.slice.call(arguments, 0); return hostCall(hostGlobal("console"), "log", join(" ", cons("[sx]", map(str, args)))); }; -PRIMITIVES["console-log"] = consoleLog; - - // now-ms - var nowMs = function() { return hostCall(hostGlobal("Date"), "now"); }; -PRIMITIVES["now-ms"] = nowMs; - - // schedule-idle - var scheduleIdle = function(f) { return (function() { - var cb = hostCallback(f); - return (isSxTruthy(hostGet(domWindow(), "requestIdleCallback")) ? hostCall(domWindow(), "requestIdleCallback", cb) : setTimeout_(cb, 0)); -})(); }; -PRIMITIVES["schedule-idle"] = scheduleIdle; - - // set-cookie - var setCookie = function(name, value, days) { return (function() { - var d = sxOr(days, 365); - var expires = hostCall(hostNew("Date", (hostCall(hostGlobal("Date"), "now") + (d * 86400000.0))), "toUTCString"); - return hostSet(domDocument(), "cookie", (String(name) + String("=") + String(hostCall(NIL, "encodeURIComponent", value)) + String(";expires=") + String(expires) + String(";path=/;SameSite=Lax"))); -})(); }; -PRIMITIVES["set-cookie"] = setCookie; - - // get-cookie - var getCookie = function(name) { return (function() { - var cookies = hostGet(domDocument(), "cookie"); - var match = hostCall(cookies, "match", hostNew("RegExp", (String("(?:^|;\\s*)") + String(name) + String("=([^;]*)")))); - return (isSxTruthy(match) ? hostCall(NIL, "decodeURIComponent", hostGet(match, 1)) : NIL); -})(); }; -PRIMITIVES["get-cookie"] = getCookie; - - // === Transpiled from adapter-dom === // SVG_NS @@ -6235,6 +5826,81 @@ PRIMITIVES["resource"] = resource; // ----------------------------------------------------------------------- PRIMITIVES["error"] = function(msg) { throw new Error(msg); }; + // FFI library functions — defined in dom.sx/browser.sx but not transpiled. + // Registered here so runtime-evaluated SX code (data-init, islands) can use them. + PRIMITIVES["prevent-default"] = preventDefault_; + PRIMITIVES["stop-propagation"] = stopPropagation_; + PRIMITIVES["event-modifier-key?"] = eventModifierKey_p; + PRIMITIVES["element-value"] = elementValue; + PRIMITIVES["error-message"] = errorMessage; + PRIMITIVES["schedule-idle"] = scheduleIdle; + PRIMITIVES["console-log"] = function() { + var args = Array.prototype.slice.call(arguments); + console.log.apply(console, ["[sx]"].concat(args)); + return args.length > 0 ? args[0] : NIL; + }; + PRIMITIVES["set-cookie"] = function(name, value, days) { + var d = days || 365; + var expires = new Date(Date.now() + d * 864e5).toUTCString(); + document.cookie = name + "=" + encodeURIComponent(value) + ";expires=" + expires + ";path=/;SameSite=Lax"; + return NIL; + }; + PRIMITIVES["get-cookie"] = function(name) { + var m = document.cookie.match(new RegExp("(?:^|;\\s*)" + name + "=([^;]*)")); + return m ? decodeURIComponent(m[1]) : NIL; + }; + + // dom.sx / browser.sx library functions — not transpiled, registered from + // native platform implementations so runtime-eval'd SX code can use them. + if (typeof domBody === "function") PRIMITIVES["dom-body"] = domBody; + if (typeof domQuery === "function") PRIMITIVES["dom-query"] = domQuery; + if (typeof domQueryAll === "function") PRIMITIVES["dom-query-all"] = domQueryAll; + if (typeof domQueryById === "function") PRIMITIVES["dom-query-by-id"] = domQueryById; + if (typeof domSetAttr === "function") PRIMITIVES["dom-set-attr"] = domSetAttr; + if (typeof domGetAttr === "function") PRIMITIVES["dom-get-attr"] = domGetAttr; + if (typeof domRemoveAttr === "function") PRIMITIVES["dom-remove-attr"] = domRemoveAttr; + if (typeof domHasAttr === "function") PRIMITIVES["dom-has-attr?"] = domHasAttr; + if (typeof domAddClass === "function") PRIMITIVES["dom-add-class"] = domAddClass; + if (typeof domRemoveClass === "function") PRIMITIVES["dom-remove-class"] = domRemoveClass; + if (typeof domHasClass === "function") PRIMITIVES["dom-has-class?"] = domHasClass; + if (typeof domClosest === "function") PRIMITIVES["dom-closest"] = domClosest; + if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches; + if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml; + if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml; + if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; + if (typeof domCreateElement === "function") PRIMITIVES["dom-create-element"] = domCreateElement; + if (typeof domAppend === "function") PRIMITIVES["dom-append"] = domAppend; + if (typeof domAppendToHead === "function") PRIMITIVES["dom-append-to-head"] = domAppendToHead; + if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; + if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; + PRIMITIVES["dom-listen"] = domListen; + PRIMITIVES["dom-dispatch"] = domDispatch; + PRIMITIVES["event-detail"] = eventDetail; + PRIMITIVES["create-text-node"] = createTextNode; + PRIMITIVES["dom-set-text-content"] = domSetTextContent; + PRIMITIVES["dom-focus"] = domFocus; + PRIMITIVES["dom-tag-name"] = domTagName; + PRIMITIVES["dom-get-prop"] = domGetProp; + PRIMITIVES["dom-set-prop"] = domSetProp; + PRIMITIVES["reactive-text"] = reactiveText; + PRIMITIVES["set-interval"] = setInterval_; + PRIMITIVES["clear-interval"] = clearInterval_; + PRIMITIVES["promise-then"] = promiseThen; + PRIMITIVES["promise-delayed"] = promiseDelayed; + PRIMITIVES["local-storage-get"] = function(key) { + try { var v = localStorage.getItem(key); return v === null ? NIL : v; } + catch (e) { return NIL; } + }; + PRIMITIVES["local-storage-set"] = function(key, val) { + try { localStorage.setItem(key, val); } catch (e) {} + return NIL; + }; + PRIMITIVES["local-storage-remove"] = function(key) { + try { localStorage.removeItem(key); } catch (e) {} + return NIL; + }; + if (typeof sxParse === "function") PRIMITIVES["sx-parse"] = sxParse; + // Platform deps functions (native JS, not transpiled — need explicit registration) PRIMITIVES["component-deps"] = componentDeps; PRIMITIVES["component-set-deps!"] = componentSetDeps; diff --git a/sx/sxc/init-client.sx.txt b/sx/sxc/init-client.sx.txt index df62aee..4468d3e 100644 --- a/sx/sxc/init-client.sx.txt +++ b/sx/sxc/init-client.sx.txt @@ -14,10 +14,10 @@ ;; CSSX flush hook — inject collected CSS rules into a