From bea071a039ac779f6d4aaddcd4394cfd9e58e4b8 Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 5 Mar 2026 13:20:29 +0000 Subject: [PATCH] Add CSSX and boot adapters to SX spec (style dictionary + browser lifecycle) - cssx.sx: on-demand CSS style dictionary (variant splitting, atom resolution, content-addressed hashing, style merging) - boot.sx: browser boot lifecycle (script processing, mount/hydrate/update, component caching, head element hoisting) - bootstrap_js.py: platform JS for cssx (FNV-1a hash, regex, CSS injection) and boot (localStorage, cookies, DOM mounting) - Rebuilt sx-browser.js (136K) and sx-ref.js (148K) with all adapters Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 548 +++++++++++++++++++++++++++- shared/static/scripts/sx-ref.js | 548 +++++++++++++++++++++++++++- shared/sx/ref/boot.sx | 384 +++++++++++++++++++ shared/sx/ref/bootstrap_js.py | 355 +++++++++++++++++- shared/sx/ref/cssx.sx | 314 ++++++++++++++++ 5 files changed, 2135 insertions(+), 14 deletions(-) create mode 100644 shared/sx/ref/boot.sx create mode 100644 shared/sx/ref/cssx.sx diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index c4288ca..221b289 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -1565,6 +1565,296 @@ var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); }; + // === Transpiled from cssx === + + // _style-atoms + var _styleAtoms = {}; + + // _pseudo-variants + var _pseudoVariants = {}; + + // _responsive-breakpoints + var _responsiveBreakpoints = {}; + + // _style-keyframes + var _styleKeyframes = {}; + + // _arbitrary-patterns + var _arbitraryPatterns = []; + + // _child-selector-prefixes + var _childSelectorPrefixes = []; + + // _style-cache + var _styleCache = {}; + + // _injected-styles + var _injectedStyles = {}; + + // load-style-dict + var loadStyleDict = function(data) { return (_styleAtoms = sxOr(get(data, "a"), {})); }; + + // split-variant + var splitVariant = function(atom) { return (function() { + var result = NIL; + { var _c = keys(_responsiveBreakpoints); for (var _i = 0; _i < _c.length; _i++) { var bp = _c[_i]; if (isSxTruthy(isNil(result))) { + (function() { + var prefix = (String(bp) + String(":")); + return (isSxTruthy(startsWith(atom, prefix)) ? (function() { + var restAtom = slice(atom, len(prefix)); + return (function() { + var innerMatch = NIL; + { var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(innerMatch))) { + (function() { + var innerPrefix = (String(pv) + String(":")); + return (isSxTruthy(startsWith(restAtom, innerPrefix)) ? (innerMatch = [(String(bp) + String(":") + String(pv)), slice(restAtom, len(innerPrefix))]) : NIL); +})(); +} } } + return (result = sxOr(innerMatch, [bp, restAtom])); +})(); +})() : NIL); +})(); +} } } + if (isSxTruthy(isNil(result))) { + { var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(result))) { + (function() { + var prefix = (String(pv) + String(":")); + return (isSxTruthy(startsWith(atom, prefix)) ? (result = [pv, slice(atom, len(prefix))]) : NIL); +})(); +} } } +} + return sxOr(result, [NIL, atom]); +})(); }; + + // resolve-atom + var resolveAtom = function(atom) { return (function() { + var decls = dictGet(_styleAtoms, atom); + return (isSxTruthy(!isNil(decls)) ? decls : (isSxTruthy(startsWith(atom, "animate-")) ? (function() { + var kfName = slice(atom, 8); + return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? (String("animation-name:") + String(kfName)) : NIL); +})() : (function() { + var matchResult = NIL; + { var _c = _arbitraryPatterns; for (var _i = 0; _i < _c.length; _i++) { var pat = _c[_i]; if (isSxTruthy(isNil(matchResult))) { + (function() { + var m = regexMatch(get(pat, "re"), atom); + return (isSxTruthy(m) ? (matchResult = regexReplaceGroups(get(pat, "tmpl"), m)) : NIL); +})(); +} } } + return matchResult; +})())); +})(); }; + + // is-child-selector-atom? + var isChildSelectorAtom = function(atom) { return some(function(prefix) { return startsWith(atom, prefix); }, _childSelectorPrefixes); }; + + // hash-style + var hashStyle = function(input) { return fnv1aHash(input); }; + + // resolve-style + var resolveStyle = function(atoms) { return (function() { + var key = join("\\0", atoms); + return (function() { + var cached = dictGet(_styleCache, key); + return (isSxTruthy(!isNil(cached)) ? cached : (function() { + var baseDecls = []; + var mediaRules = []; + var pseudoRules = []; + var kfNeeded = []; + { var _c = atoms; for (var _i = 0; _i < _c.length; _i++) { var a = _c[_i]; if (isSxTruthy(a)) { + (function() { + var clean = (isSxTruthy(startsWith(a, ":")) ? slice(a, 1) : a); + return (function() { + var parts = splitVariant(clean); + return (function() { + var variant = first(parts); + var base = nth(parts, 1); + var decls = resolveAtom(base); + return (isSxTruthy(decls) ? ((isSxTruthy(startsWith(base, "animate-")) ? (function() { + var kfName = slice(base, 8); + return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? append_b(kfNeeded, [kfName, dictGet(_styleKeyframes, kfName)]) : NIL); +})() : NIL), (isSxTruthy(isNil(variant)) ? append_b(baseDecls, decls) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() { + var vparts = split(variant, ":"); + var mediaPart = NIL; + var pseudoPart = NIL; + { var _c = vparts; for (var _i = 0; _i < _c.length; _i++) { var vp = _c[_i]; (isSxTruthy(dictHas(_responsiveBreakpoints, vp)) ? (mediaPart = dictGet(_responsiveBreakpoints, vp)) : (isSxTruthy(dictHas(_pseudoVariants, vp)) ? (pseudoPart = dictGet(_pseudoVariants, vp)) : NIL)); } } + if (isSxTruthy(mediaPart)) { + mediaRules.push([mediaPart, decls]); +} + if (isSxTruthy(pseudoPart)) { + pseudoRules.push([pseudoPart, decls]); +} + return (isSxTruthy((isSxTruthy(isNil(mediaPart)) && isNil(pseudoPart))) ? append_b(baseDecls, decls) : NIL); +})())))) : NIL); +})(); +})(); +})(); +} } } + return (function() { + var hashInput = join(";", baseDecls); + { var _c = chunkEvery(mediaRules, 2); for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } + { var _c = chunkEvery(pseudoRules, 2); for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } + { var _c = chunkEvery(kfNeeded, 2); for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } + return (function() { + var cn = (String("sx-") + String(hashStyle(hashInput))); + var sv = makeStyleValue_(cn, join(";", baseDecls), chunkEvery(mediaRules, 2), chunkEvery(pseudoRules, 2), chunkEvery(kfNeeded, 2)); + _styleCache[key] = sv; + injectStyleValue(sv, atoms); + return sv; +})(); +})(); +})()); +})(); +})(); }; + + // merge-style-values + var mergeStyleValues = function(styles) { return (isSxTruthy((len(styles) == 1)) ? first(styles) : (function() { + var allDecls = []; + var allMedia = []; + var allPseudo = []; + var allKf = []; + { var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var sv = _c[_i]; if (isSxTruthy(styleValueDeclarations(sv))) { + allDecls.push(styleValueDeclarations(sv)); +} } } + return (function() { + var hashInput = join(";", allDecls); + { var _c = allMedia; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } + { var _c = allPseudo; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } + { var _c = allKf; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } + return (function() { + var cn = (String("sx-") + String(hashStyle(hashInput))); + var merged = makeStyleValue_(cn, join(";", allDecls), allMedia, allPseudo, allKf); + injectStyleValue(merged, []); + return merged; +})(); +})(); +})()); }; + + + // === Transpiled from boot === + + // HEAD_HOIST_SELECTOR + var HEAD_HOIST_SELECTOR = "meta, title, link[rel='canonical'], script[type='application/ld+json']"; + + // hoist-head-elements-full + var hoistHeadElementsFull = function(root) { return (function() { + var els = domQueryAll(root, HEAD_HOIST_SELECTOR); + return forEach(function(el) { return (function() { + var tag = lower(domTagName(el)); + return (isSxTruthy((tag == "title")) ? (setDocumentTitle(domTextContent(el)), domRemoveChild(domParent(el), el)) : (isSxTruthy((tag == "meta")) ? ((function() { + var name = domGetAttr(el, "name"); + var prop = domGetAttr(el, "property"); + if (isSxTruthy(name)) { + removeHeadElement((String("meta[name=\"") + String(name) + String("\"]"))); +} + return (isSxTruthy(prop) ? removeHeadElement((String("meta[property=\"") + String(prop) + String("\"]"))) : NIL); +})(), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (isSxTruthy((isSxTruthy((tag == "link")) && (domGetAttr(el, "rel") == "canonical"))) ? (removeHeadElement("link[rel=\"canonical\"]"), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (domRemoveChild(domParent(el), el), domAppendToHead(el))))); +})(); }, els); +})(); }; + + // sx-mount + var sxMount = function(target, source, extraEnv) { return (function() { + var el = resolveMountTarget(target); + return (isSxTruthy(el) ? (function() { + var node = sxRenderWithEnv(source, extraEnv); + domSetTextContent(el, ""); + domAppend(el, node); + hoistHeadElementsFull(el); + processElements(el); + return sxHydrateElements(el); +})() : NIL); +})(); }; + + // sx-hydrate-elements + var sxHydrateElements = function(root) { return (function() { + var els = domQueryAll(sxOr(root, domBody()), "[data-sx]"); + return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "hydrated")) ? (markProcessed(el, "hydrated"), sxUpdateElement(el, NIL)) : NIL); }, els); +})(); }; + + // sx-update-element + var sxUpdateElement = function(el, newEnv) { return (function() { + var target = resolveMountTarget(el); + return (isSxTruthy(target) ? (function() { + var source = domGetAttr(target, "data-sx"); + return (isSxTruthy(source) ? (function() { + var baseEnv = parseEnvAttr(target); + var env = mergeEnvs(baseEnv, newEnv); + return (function() { + var node = sxRenderWithEnv(source, env); + domSetTextContent(target, ""); + domAppend(target, node); + return (isSxTruthy(newEnv) ? storeEnvAttr(target, baseEnv, newEnv) : NIL); +})(); +})() : NIL); +})() : NIL); +})(); }; + + // sx-render-component + var sxRenderComponent = function(name, kwargs, extraEnv) { return (function() { + var fullName = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + return (function() { + var env = getRenderEnv(extraEnv); + var comp = envGet(env, fullName); + return (isSxTruthy(!isComponent(comp)) ? error((String("Unknown component: ") + String(fullName))) : (function() { + var callExpr = [makeSymbol(fullName)]; + { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); } } + return renderToDom(callExpr, env, NIL); +})()); +})(); +})(); }; + + // process-sx-scripts + var processSxScripts = function(root) { return (function() { + var scripts = querySxScripts(root); + return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "script")) ? (markProcessed(s, "script"), (function() { + var text = domTextContent(s); + return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() { + var mountSel = domGetAttr(s, "data-mount"); + var target = domQuery(mountSel); + return (isSxTruthy(target) ? sxMount(target, text, NIL) : NIL); +})() : sxLoadComponents(text)))); +})()) : NIL); }, scripts); +})(); }; + + // process-component-script + var processComponentScript = function(script, text) { return (function() { + var hash = domGetAttr(script, "data-hash"); + return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? sxLoadComponents(text) : NIL) : (function() { + var hasInline = (isSxTruthy(text) && !isEmpty(trim(text))); + (function() { + var cachedHash = localStorageGet("sx-components-hash"); + return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo("components: downloaded (cookie stale)")) : (function() { + var cached = localStorageGet("sx-components-src"); + return (isSxTruthy(cached) ? (sxLoadComponents(cached), logInfo((String("components: cached (") + String(hash) + String(")")))) : (clearSxCompCookie(), browserReload())); +})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo((String("components: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-components-hash"), localStorageRemove("sx-components-src"), clearSxCompCookie(), browserReload()))); +})(); + return setSxCompCookie(hash); +})()); +})(); }; + + // init-style-dict + var initStyleDict = function() { return (function() { + var scripts = queryStyleScripts(); + return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "styles")) ? (markProcessed(s, "styles"), (function() { + var text = domTextContent(s); + var hash = domGetAttr(s, "data-hash"); + return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? parseAndLoadStyleDict(text) : NIL) : (function() { + var hasInline = (isSxTruthy(text) && !isEmpty(trim(text))); + (function() { + var cachedHash = localStorageGet("sx-styles-hash"); + return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo("styles: downloaded (cookie stale)")) : (function() { + var cached = localStorageGet("sx-styles-src"); + return (isSxTruthy(cached) ? (parseAndLoadStyleDict(cached), logInfo((String("styles: cached (") + String(hash) + String(")")))) : (clearSxStylesCookie(), browserReload())); +})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-hash", hash), localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo((String("styles: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-styles-hash"), localStorageRemove("sx-styles-src"), clearSxStylesCookie(), browserReload()))); +})(); + return setSxStylesCookie(hash); +})()); +})()) : NIL); }, scripts); +})(); }; + + // boot-init + var bootInit = function() { return (initCssTracking(), initStyleDict(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); }; + + // ========================================================================= // Platform interface — DOM adapter (browser-only) // ========================================================================= @@ -2317,6 +2607,252 @@ } + // ========================================================================= + // Platform interface — CSSX (style dictionary) + // ========================================================================= + + function fnv1aHash(input) { + var h = 0x811c9dc5; + for (var i = 0; i < input.length; i++) { + h ^= input.charCodeAt(i); + h = (h * 0x01000193) >>> 0; + } + return h.toString(16).padStart(8, "0").substring(0, 6); + } + + function compileRegex(pattern) { + try { return new RegExp(pattern); } catch (e) { return null; } + } + + function regexMatch(re, s) { + if (!re) return NIL; + var m = s.match(re); + return m ? Array.prototype.slice.call(m) : NIL; + } + + function regexReplaceGroups(tmpl, match) { + var result = tmpl; + for (var j = 1; j < match.length; j++) { + result = result.split("{" + (j - 1) + "}").join(match[j]); + } + return result; + } + + function makeStyleValue_(cn, decls, media, pseudo, kf) { + return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []); + } + + function styleValueDeclarations(sv) { return sv.declarations; } + function styleValueMediaRules(sv) { return sv.mediaRules; } + function styleValuePseudoRules(sv) { return sv.pseudoRules; } + function styleValueKeyframes_(sv) { return sv.keyframes; } + + function injectStyleValue(sv, atoms) { + if (_injectedStyles[sv.className]) return; + _injectedStyles[sv.className] = true; + + if (!_hasDom) return; + var cssTarget = document.getElementById("sx-css"); + if (!cssTarget) return; + + var rules = []; + if (sv.declarations) { + var hasChild = false; + if (atoms) { + for (var ai = 0; ai < atoms.length; ai++) { + if (isChildSelectorAtom(atoms[ai])) { hasChild = true; break; } + } + } + if (hasChild) { + rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}"); + } else { + rules.push("." + sv.className + "{" + sv.declarations + "}"); + } + } + for (var pi = 0; pi < sv.pseudoRules.length; pi++) { + var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1]; + if (sel.indexOf("&") >= 0) { + rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}"); + } else { + rules.push("." + sv.className + sel + "{" + decls + "}"); + } + } + for (var mi = 0; mi < sv.mediaRules.length; mi++) { + rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}"); + } + for (var ki = 0; ki < sv.keyframes.length; ki++) { + rules.push(sv.keyframes[ki][1]); + } + cssTarget.textContent += rules.join(""); + } + + // Replace stub css primitive with real CSSX implementation + PRIMITIVES["css"] = function() { + var atoms = []; + for (var i = 0; i < arguments.length; i++) { + var a = arguments[i]; + if (isNil(a) || a === false) continue; + atoms.push(isKw(a) ? a.name : String(a)); + } + if (!atoms.length) return NIL; + return resolveStyle(atoms); + }; + + PRIMITIVES["merge-styles"] = function() { + var valid = []; + for (var i = 0; i < arguments.length; i++) { + if (isStyleValue(arguments[i])) valid.push(arguments[i]); + } + if (!valid.length) return NIL; + if (valid.length === 1) return valid[0]; + return mergeStyleValues(valid); + }; + + + // ========================================================================= + // Platform interface — Boot (mount, hydrate, scripts, cookies) + // ========================================================================= + + function resolveMountTarget(target) { + if (typeof target === "string") return _hasDom ? document.querySelector(target) : null; + return target; + } + + function sxRenderWithEnv(source, extraEnv) { + var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + var exprs = parse(source); + if (!_hasDom) return null; + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) { + var node = renderToDom(exprs[i], env, null); + if (node) frag.appendChild(node); + } + return frag; + } + + function getRenderEnv(extraEnv) { + return extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + } + + function mergeEnvs(base, newEnv) { + return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base); + } + + function sxLoadComponents(text) { + try { + var exprs = parse(text); + for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv)); + } catch (err) { + logParseError("loadComponents", text, err); + throw err; + } + } + + function setDocumentTitle(s) { + if (_hasDom) document.title = s || ""; + } + + function removeHeadElement(sel) { + if (!_hasDom) return; + var old = document.head.querySelector(sel); + if (old) old.parentNode.removeChild(old); + } + + function querySxScripts(root) { + if (!_hasDom) return []; + return Array.prototype.slice.call( + (root || document).querySelectorAll('script[type="text/sx"]')); + } + + function queryStyleScripts() { + if (!_hasDom) return []; + return Array.prototype.slice.call( + document.querySelectorAll('script[type="text/sx-styles"]')); + } + + // --- localStorage --- + + function localStorageGet(key) { + try { var v = localStorage.getItem(key); return v === null ? NIL : v; } + catch (e) { return NIL; } + } + + function localStorageSet(key, val) { + try { localStorage.setItem(key, val); } catch (e) {} + } + + function localStorageRemove(key) { + try { localStorage.removeItem(key); } catch (e) {} + } + + // --- Cookies --- + + function setSxCompCookie(hash) { + if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; + } + + function clearSxCompCookie() { + if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax"; + } + + function setSxStylesCookie(hash) { + if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; + } + + function clearSxStylesCookie() { + if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax"; + } + + // --- Env helpers --- + + function parseEnvAttr(el) { + var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null; + if (!attr) return {}; + try { return JSON.parse(attr); } catch (e) { return {}; } + } + + function storeEnvAttr(el, base, newEnv) { + var merged = merge(base, newEnv); + if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged)); + } + + function toKebab(s) { return s.replace(/_/g, "-"); } + + // --- Logging --- + + function logInfo(msg) { + if (typeof console !== "undefined") console.log("[sx-ref] " + msg); + } + + function logParseError(label, text, err) { + if (typeof console === "undefined") return; + var msg = err && err.message ? err.message : String(err); + var colMatch = msg.match(/col (\d+)/); + var lineMatch = msg.match(/line (\d+)/); + if (colMatch && text) { + var errLine = lineMatch ? parseInt(lineMatch[1]) : 1; + var errCol = parseInt(colMatch[1]); + var lines = text.split("\n"); + var pos = 0; + for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1; + pos += errCol; + var ws = 80; + var start = Math.max(0, pos - ws); + var end = Math.min(text.length, pos + ws); + console.error("[sx-ref] " + label + ":", msg, + "\n around error (pos ~" + pos + "):", + "\n \u00ab" + text.substring(start, pos) + "\u26d4" + text.substring(pos, end) + "\u00bb"); + } else { + console.error("[sx-ref] " + label + ":", msg); + } + } + + function parseAndLoadStyleDict(text) { + try { loadStyleDict(JSON.parse(text)); } + catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); } + } + + // ========================================================================= // Post-transpilation fixups // ========================================================================= @@ -2472,8 +3008,14 @@ process: typeof processElements === "function" ? processElements : null, executeRequest: typeof executeRequest === "function" ? executeRequest : null, postSwap: typeof postSwap === "function" ? postSwap : null, - init: typeof engineInit === "function" ? engineInit : null, - _version: "ref-2.0 (dom+engine+orchestration, bootstrap-compiled)" + processScripts: typeof processSxScripts === "function" ? processSxScripts : null, + mount: typeof sxMount === "function" ? sxMount : null, + hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null, + update: typeof sxUpdateElement === "function" ? sxUpdateElement : null, + renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null, + getEnv: function() { return componentEnv; }, + init: typeof bootInit === "function" ? bootInit : null, + _version: "ref-2.0 (boot+cssx+dom+engine+orchestration, bootstrap-compiled)" }; @@ -2486,7 +3028,7 @@ // --- Auto-init --- if (typeof document !== "undefined") { - var _sxRefInit = function() { engineInit(); }; + var _sxRefInit = function() { bootInit(); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", _sxRefInit); } else { diff --git a/shared/static/scripts/sx-ref.js b/shared/static/scripts/sx-ref.js index e7dc6ea..47c31a3 100644 --- a/shared/static/scripts/sx-ref.js +++ b/shared/static/scripts/sx-ref.js @@ -1713,6 +1713,296 @@ var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); }; + // === Transpiled from cssx === + + // _style-atoms + var _styleAtoms = {}; + + // _pseudo-variants + var _pseudoVariants = {}; + + // _responsive-breakpoints + var _responsiveBreakpoints = {}; + + // _style-keyframes + var _styleKeyframes = {}; + + // _arbitrary-patterns + var _arbitraryPatterns = []; + + // _child-selector-prefixes + var _childSelectorPrefixes = []; + + // _style-cache + var _styleCache = {}; + + // _injected-styles + var _injectedStyles = {}; + + // load-style-dict + var loadStyleDict = function(data) { return (_styleAtoms = sxOr(get(data, "a"), {})); }; + + // split-variant + var splitVariant = function(atom) { return (function() { + var result = NIL; + { var _c = keys(_responsiveBreakpoints); for (var _i = 0; _i < _c.length; _i++) { var bp = _c[_i]; if (isSxTruthy(isNil(result))) { + (function() { + var prefix = (String(bp) + String(":")); + return (isSxTruthy(startsWith(atom, prefix)) ? (function() { + var restAtom = slice(atom, len(prefix)); + return (function() { + var innerMatch = NIL; + { var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(innerMatch))) { + (function() { + var innerPrefix = (String(pv) + String(":")); + return (isSxTruthy(startsWith(restAtom, innerPrefix)) ? (innerMatch = [(String(bp) + String(":") + String(pv)), slice(restAtom, len(innerPrefix))]) : NIL); +})(); +} } } + return (result = sxOr(innerMatch, [bp, restAtom])); +})(); +})() : NIL); +})(); +} } } + if (isSxTruthy(isNil(result))) { + { var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(result))) { + (function() { + var prefix = (String(pv) + String(":")); + return (isSxTruthy(startsWith(atom, prefix)) ? (result = [pv, slice(atom, len(prefix))]) : NIL); +})(); +} } } +} + return sxOr(result, [NIL, atom]); +})(); }; + + // resolve-atom + var resolveAtom = function(atom) { return (function() { + var decls = dictGet(_styleAtoms, atom); + return (isSxTruthy(!isNil(decls)) ? decls : (isSxTruthy(startsWith(atom, "animate-")) ? (function() { + var kfName = slice(atom, 8); + return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? (String("animation-name:") + String(kfName)) : NIL); +})() : (function() { + var matchResult = NIL; + { var _c = _arbitraryPatterns; for (var _i = 0; _i < _c.length; _i++) { var pat = _c[_i]; if (isSxTruthy(isNil(matchResult))) { + (function() { + var m = regexMatch(get(pat, "re"), atom); + return (isSxTruthy(m) ? (matchResult = regexReplaceGroups(get(pat, "tmpl"), m)) : NIL); +})(); +} } } + return matchResult; +})())); +})(); }; + + // is-child-selector-atom? + var isChildSelectorAtom = function(atom) { return some(function(prefix) { return startsWith(atom, prefix); }, _childSelectorPrefixes); }; + + // hash-style + var hashStyle = function(input) { return fnv1aHash(input); }; + + // resolve-style + var resolveStyle = function(atoms) { return (function() { + var key = join("\\0", atoms); + return (function() { + var cached = dictGet(_styleCache, key); + return (isSxTruthy(!isNil(cached)) ? cached : (function() { + var baseDecls = []; + var mediaRules = []; + var pseudoRules = []; + var kfNeeded = []; + { var _c = atoms; for (var _i = 0; _i < _c.length; _i++) { var a = _c[_i]; if (isSxTruthy(a)) { + (function() { + var clean = (isSxTruthy(startsWith(a, ":")) ? slice(a, 1) : a); + return (function() { + var parts = splitVariant(clean); + return (function() { + var variant = first(parts); + var base = nth(parts, 1); + var decls = resolveAtom(base); + return (isSxTruthy(decls) ? ((isSxTruthy(startsWith(base, "animate-")) ? (function() { + var kfName = slice(base, 8); + return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? append_b(kfNeeded, [kfName, dictGet(_styleKeyframes, kfName)]) : NIL); +})() : NIL), (isSxTruthy(isNil(variant)) ? append_b(baseDecls, decls) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() { + var vparts = split(variant, ":"); + var mediaPart = NIL; + var pseudoPart = NIL; + { var _c = vparts; for (var _i = 0; _i < _c.length; _i++) { var vp = _c[_i]; (isSxTruthy(dictHas(_responsiveBreakpoints, vp)) ? (mediaPart = dictGet(_responsiveBreakpoints, vp)) : (isSxTruthy(dictHas(_pseudoVariants, vp)) ? (pseudoPart = dictGet(_pseudoVariants, vp)) : NIL)); } } + if (isSxTruthy(mediaPart)) { + mediaRules.push([mediaPart, decls]); +} + if (isSxTruthy(pseudoPart)) { + pseudoRules.push([pseudoPart, decls]); +} + return (isSxTruthy((isSxTruthy(isNil(mediaPart)) && isNil(pseudoPart))) ? append_b(baseDecls, decls) : NIL); +})())))) : NIL); +})(); +})(); +})(); +} } } + return (function() { + var hashInput = join(";", baseDecls); + { var _c = chunkEvery(mediaRules, 2); for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } + { var _c = chunkEvery(pseudoRules, 2); for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } + { var _c = chunkEvery(kfNeeded, 2); for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } + return (function() { + var cn = (String("sx-") + String(hashStyle(hashInput))); + var sv = makeStyleValue_(cn, join(";", baseDecls), chunkEvery(mediaRules, 2), chunkEvery(pseudoRules, 2), chunkEvery(kfNeeded, 2)); + _styleCache[key] = sv; + injectStyleValue(sv, atoms); + return sv; +})(); +})(); +})()); +})(); +})(); }; + + // merge-style-values + var mergeStyleValues = function(styles) { return (isSxTruthy((len(styles) == 1)) ? first(styles) : (function() { + var allDecls = []; + var allMedia = []; + var allPseudo = []; + var allKf = []; + { var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var sv = _c[_i]; if (isSxTruthy(styleValueDeclarations(sv))) { + allDecls.push(styleValueDeclarations(sv)); +} } } + return (function() { + var hashInput = join(";", allDecls); + { var _c = allMedia; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } + { var _c = allPseudo; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } + { var _c = allKf; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } + return (function() { + var cn = (String("sx-") + String(hashStyle(hashInput))); + var merged = makeStyleValue_(cn, join(";", allDecls), allMedia, allPseudo, allKf); + injectStyleValue(merged, []); + return merged; +})(); +})(); +})()); }; + + + // === Transpiled from boot === + + // HEAD_HOIST_SELECTOR + var HEAD_HOIST_SELECTOR = "meta, title, link[rel='canonical'], script[type='application/ld+json']"; + + // hoist-head-elements-full + var hoistHeadElementsFull = function(root) { return (function() { + var els = domQueryAll(root, HEAD_HOIST_SELECTOR); + return forEach(function(el) { return (function() { + var tag = lower(domTagName(el)); + return (isSxTruthy((tag == "title")) ? (setDocumentTitle(domTextContent(el)), domRemoveChild(domParent(el), el)) : (isSxTruthy((tag == "meta")) ? ((function() { + var name = domGetAttr(el, "name"); + var prop = domGetAttr(el, "property"); + if (isSxTruthy(name)) { + removeHeadElement((String("meta[name=\"") + String(name) + String("\"]"))); +} + return (isSxTruthy(prop) ? removeHeadElement((String("meta[property=\"") + String(prop) + String("\"]"))) : NIL); +})(), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (isSxTruthy((isSxTruthy((tag == "link")) && (domGetAttr(el, "rel") == "canonical"))) ? (removeHeadElement("link[rel=\"canonical\"]"), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (domRemoveChild(domParent(el), el), domAppendToHead(el))))); +})(); }, els); +})(); }; + + // sx-mount + var sxMount = function(target, source, extraEnv) { return (function() { + var el = resolveMountTarget(target); + return (isSxTruthy(el) ? (function() { + var node = sxRenderWithEnv(source, extraEnv); + domSetTextContent(el, ""); + domAppend(el, node); + hoistHeadElementsFull(el); + processElements(el); + return sxHydrateElements(el); +})() : NIL); +})(); }; + + // sx-hydrate-elements + var sxHydrateElements = function(root) { return (function() { + var els = domQueryAll(sxOr(root, domBody()), "[data-sx]"); + return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "hydrated")) ? (markProcessed(el, "hydrated"), sxUpdateElement(el, NIL)) : NIL); }, els); +})(); }; + + // sx-update-element + var sxUpdateElement = function(el, newEnv) { return (function() { + var target = resolveMountTarget(el); + return (isSxTruthy(target) ? (function() { + var source = domGetAttr(target, "data-sx"); + return (isSxTruthy(source) ? (function() { + var baseEnv = parseEnvAttr(target); + var env = mergeEnvs(baseEnv, newEnv); + return (function() { + var node = sxRenderWithEnv(source, env); + domSetTextContent(target, ""); + domAppend(target, node); + return (isSxTruthy(newEnv) ? storeEnvAttr(target, baseEnv, newEnv) : NIL); +})(); +})() : NIL); +})() : NIL); +})(); }; + + // sx-render-component + var sxRenderComponent = function(name, kwargs, extraEnv) { return (function() { + var fullName = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); + return (function() { + var env = getRenderEnv(extraEnv); + var comp = envGet(env, fullName); + return (isSxTruthy(!isComponent(comp)) ? error((String("Unknown component: ") + String(fullName))) : (function() { + var callExpr = [makeSymbol(fullName)]; + { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); } } + return renderToDom(callExpr, env, NIL); +})()); +})(); +})(); }; + + // process-sx-scripts + var processSxScripts = function(root) { return (function() { + var scripts = querySxScripts(root); + return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "script")) ? (markProcessed(s, "script"), (function() { + var text = domTextContent(s); + return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() { + var mountSel = domGetAttr(s, "data-mount"); + var target = domQuery(mountSel); + return (isSxTruthy(target) ? sxMount(target, text, NIL) : NIL); +})() : sxLoadComponents(text)))); +})()) : NIL); }, scripts); +})(); }; + + // process-component-script + var processComponentScript = function(script, text) { return (function() { + var hash = domGetAttr(script, "data-hash"); + return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? sxLoadComponents(text) : NIL) : (function() { + var hasInline = (isSxTruthy(text) && !isEmpty(trim(text))); + (function() { + var cachedHash = localStorageGet("sx-components-hash"); + return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo("components: downloaded (cookie stale)")) : (function() { + var cached = localStorageGet("sx-components-src"); + return (isSxTruthy(cached) ? (sxLoadComponents(cached), logInfo((String("components: cached (") + String(hash) + String(")")))) : (clearSxCompCookie(), browserReload())); +})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo((String("components: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-components-hash"), localStorageRemove("sx-components-src"), clearSxCompCookie(), browserReload()))); +})(); + return setSxCompCookie(hash); +})()); +})(); }; + + // init-style-dict + var initStyleDict = function() { return (function() { + var scripts = queryStyleScripts(); + return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "styles")) ? (markProcessed(s, "styles"), (function() { + var text = domTextContent(s); + var hash = domGetAttr(s, "data-hash"); + return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? parseAndLoadStyleDict(text) : NIL) : (function() { + var hasInline = (isSxTruthy(text) && !isEmpty(trim(text))); + (function() { + var cachedHash = localStorageGet("sx-styles-hash"); + return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo("styles: downloaded (cookie stale)")) : (function() { + var cached = localStorageGet("sx-styles-src"); + return (isSxTruthy(cached) ? (parseAndLoadStyleDict(cached), logInfo((String("styles: cached (") + String(hash) + String(")")))) : (clearSxStylesCookie(), browserReload())); +})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-hash", hash), localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo((String("styles: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-styles-hash"), localStorageRemove("sx-styles-src"), clearSxStylesCookie(), browserReload()))); +})(); + return setSxStylesCookie(hash); +})()); +})()) : NIL); }, scripts); +})(); }; + + // boot-init + var bootInit = function() { return (initCssTracking(), initStyleDict(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); }; + + // ========================================================================= // Platform interface — DOM adapter (browser-only) // ========================================================================= @@ -2465,6 +2755,252 @@ } + // ========================================================================= + // Platform interface — CSSX (style dictionary) + // ========================================================================= + + function fnv1aHash(input) { + var h = 0x811c9dc5; + for (var i = 0; i < input.length; i++) { + h ^= input.charCodeAt(i); + h = (h * 0x01000193) >>> 0; + } + return h.toString(16).padStart(8, "0").substring(0, 6); + } + + function compileRegex(pattern) { + try { return new RegExp(pattern); } catch (e) { return null; } + } + + function regexMatch(re, s) { + if (!re) return NIL; + var m = s.match(re); + return m ? Array.prototype.slice.call(m) : NIL; + } + + function regexReplaceGroups(tmpl, match) { + var result = tmpl; + for (var j = 1; j < match.length; j++) { + result = result.split("{" + (j - 1) + "}").join(match[j]); + } + return result; + } + + function makeStyleValue_(cn, decls, media, pseudo, kf) { + return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []); + } + + function styleValueDeclarations(sv) { return sv.declarations; } + function styleValueMediaRules(sv) { return sv.mediaRules; } + function styleValuePseudoRules(sv) { return sv.pseudoRules; } + function styleValueKeyframes_(sv) { return sv.keyframes; } + + function injectStyleValue(sv, atoms) { + if (_injectedStyles[sv.className]) return; + _injectedStyles[sv.className] = true; + + if (!_hasDom) return; + var cssTarget = document.getElementById("sx-css"); + if (!cssTarget) return; + + var rules = []; + if (sv.declarations) { + var hasChild = false; + if (atoms) { + for (var ai = 0; ai < atoms.length; ai++) { + if (isChildSelectorAtom(atoms[ai])) { hasChild = true; break; } + } + } + if (hasChild) { + rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}"); + } else { + rules.push("." + sv.className + "{" + sv.declarations + "}"); + } + } + for (var pi = 0; pi < sv.pseudoRules.length; pi++) { + var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1]; + if (sel.indexOf("&") >= 0) { + rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}"); + } else { + rules.push("." + sv.className + sel + "{" + decls + "}"); + } + } + for (var mi = 0; mi < sv.mediaRules.length; mi++) { + rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}"); + } + for (var ki = 0; ki < sv.keyframes.length; ki++) { + rules.push(sv.keyframes[ki][1]); + } + cssTarget.textContent += rules.join(""); + } + + // Replace stub css primitive with real CSSX implementation + PRIMITIVES["css"] = function() { + var atoms = []; + for (var i = 0; i < arguments.length; i++) { + var a = arguments[i]; + if (isNil(a) || a === false) continue; + atoms.push(isKw(a) ? a.name : String(a)); + } + if (!atoms.length) return NIL; + return resolveStyle(atoms); + }; + + PRIMITIVES["merge-styles"] = function() { + var valid = []; + for (var i = 0; i < arguments.length; i++) { + if (isStyleValue(arguments[i])) valid.push(arguments[i]); + } + if (!valid.length) return NIL; + if (valid.length === 1) return valid[0]; + return mergeStyleValues(valid); + }; + + + // ========================================================================= + // Platform interface — Boot (mount, hydrate, scripts, cookies) + // ========================================================================= + + function resolveMountTarget(target) { + if (typeof target === "string") return _hasDom ? document.querySelector(target) : null; + return target; + } + + function sxRenderWithEnv(source, extraEnv) { + var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + var exprs = parse(source); + if (!_hasDom) return null; + var frag = document.createDocumentFragment(); + for (var i = 0; i < exprs.length; i++) { + var node = renderToDom(exprs[i], env, null); + if (node) frag.appendChild(node); + } + return frag; + } + + function getRenderEnv(extraEnv) { + return extraEnv ? merge(componentEnv, extraEnv) : componentEnv; + } + + function mergeEnvs(base, newEnv) { + return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base); + } + + function sxLoadComponents(text) { + try { + var exprs = parse(text); + for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv)); + } catch (err) { + logParseError("loadComponents", text, err); + throw err; + } + } + + function setDocumentTitle(s) { + if (_hasDom) document.title = s || ""; + } + + function removeHeadElement(sel) { + if (!_hasDom) return; + var old = document.head.querySelector(sel); + if (old) old.parentNode.removeChild(old); + } + + function querySxScripts(root) { + if (!_hasDom) return []; + return Array.prototype.slice.call( + (root || document).querySelectorAll('script[type="text/sx"]')); + } + + function queryStyleScripts() { + if (!_hasDom) return []; + return Array.prototype.slice.call( + document.querySelectorAll('script[type="text/sx-styles"]')); + } + + // --- localStorage --- + + function localStorageGet(key) { + try { var v = localStorage.getItem(key); return v === null ? NIL : v; } + catch (e) { return NIL; } + } + + function localStorageSet(key, val) { + try { localStorage.setItem(key, val); } catch (e) {} + } + + function localStorageRemove(key) { + try { localStorage.removeItem(key); } catch (e) {} + } + + // --- Cookies --- + + function setSxCompCookie(hash) { + if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; + } + + function clearSxCompCookie() { + if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax"; + } + + function setSxStylesCookie(hash) { + if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; + } + + function clearSxStylesCookie() { + if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax"; + } + + // --- Env helpers --- + + function parseEnvAttr(el) { + var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null; + if (!attr) return {}; + try { return JSON.parse(attr); } catch (e) { return {}; } + } + + function storeEnvAttr(el, base, newEnv) { + var merged = merge(base, newEnv); + if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged)); + } + + function toKebab(s) { return s.replace(/_/g, "-"); } + + // --- Logging --- + + function logInfo(msg) { + if (typeof console !== "undefined") console.log("[sx-ref] " + msg); + } + + function logParseError(label, text, err) { + if (typeof console === "undefined") return; + var msg = err && err.message ? err.message : String(err); + var colMatch = msg.match(/col (\d+)/); + var lineMatch = msg.match(/line (\d+)/); + if (colMatch && text) { + var errLine = lineMatch ? parseInt(lineMatch[1]) : 1; + var errCol = parseInt(colMatch[1]); + var lines = text.split("\n"); + var pos = 0; + for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1; + pos += errCol; + var ws = 80; + var start = Math.max(0, pos - ws); + var end = Math.min(text.length, pos + ws); + console.error("[sx-ref] " + label + ":", msg, + "\n around error (pos ~" + pos + "):", + "\n \u00ab" + text.substring(start, pos) + "\u26d4" + text.substring(pos, end) + "\u00bb"); + } else { + console.error("[sx-ref] " + label + ":", msg); + } + } + + function parseAndLoadStyleDict(text) { + try { loadStyleDict(JSON.parse(text)); } + catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); } + } + + // ========================================================================= // Post-transpilation fixups // ========================================================================= @@ -2638,8 +3174,14 @@ process: typeof processElements === "function" ? processElements : null, executeRequest: typeof executeRequest === "function" ? executeRequest : null, postSwap: typeof postSwap === "function" ? postSwap : null, - init: typeof engineInit === "function" ? engineInit : null, - _version: "ref-2.0 (dom+engine+html+orchestration+sx, bootstrap-compiled)" + processScripts: typeof processSxScripts === "function" ? processSxScripts : null, + mount: typeof sxMount === "function" ? sxMount : null, + hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null, + update: typeof sxUpdateElement === "function" ? sxUpdateElement : null, + renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null, + getEnv: function() { return componentEnv; }, + init: typeof bootInit === "function" ? bootInit : null, + _version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+sx, bootstrap-compiled)" }; @@ -2652,7 +3194,7 @@ // --- Auto-init --- if (typeof document !== "undefined") { - var _sxRefInit = function() { engineInit(); }; + var _sxRefInit = function() { bootInit(); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", _sxRefInit); } else { diff --git a/shared/sx/ref/boot.sx b/shared/sx/ref/boot.sx new file mode 100644 index 0000000..94dc7dc --- /dev/null +++ b/shared/sx/ref/boot.sx @@ -0,0 +1,384 @@ +;; ========================================================================== +;; boot.sx — Browser boot, mount, hydrate, script processing +;; +;; Handles the browser startup lifecycle: +;; 1. CSS tracking init +;; 2. Style dictionary loading (from