diff --git a/.publish/ai/package.json b/.publish/ai/package.json index 30d53aa0..61869a09 100644 --- a/.publish/ai/package.json +++ b/.publish/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "license": "MIT", diff --git a/.publish/authz/package.json b/.publish/authz/package.json index a5be7595..4b6da46d 100644 --- a/.publish/authz/package.json +++ b/.publish/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/authz — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/cli/package.json b/.publish/cli/package.json index e38d70e8..f56a92d6 100644 --- a/.publish/cli/package.json +++ b/.publish/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/cli — part of the WrNexus framework.", "license": "MIT", @@ -28,16 +28,16 @@ "wrnexus": "./dist/index.js" }, "dependencies": { - "@wrnexus/core": "^0.2.45", - "@wrnexus/router": "^0.2.45", - "@wrnexus/csr": "^0.2.45", - "@wrnexus/compiler": "^0.2.45", - "@wrnexus/styles": "^0.2.45", - "@wrnexus/dev-server": "^0.2.45", - "@wrnexus/ui": "^0.2.45", - "@wrnexus/validation": "^0.2.45", - "@wrnexus/i18n": "^0.2.45", - "@wrnexus/db": "^0.2.45" + "@wrnexus/core": "^0.2.46", + "@wrnexus/router": "^0.2.46", + "@wrnexus/csr": "^0.2.46", + "@wrnexus/compiler": "^0.2.46", + "@wrnexus/styles": "^0.2.46", + "@wrnexus/dev-server": "^0.2.46", + "@wrnexus/ui": "^0.2.46", + "@wrnexus/validation": "^0.2.46", + "@wrnexus/i18n": "^0.2.46", + "@wrnexus/db": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/compiler/package.json b/.publish/compiler/package.json index ceb4a6dc..218bc9c9 100644 --- a/.publish/compiler/package.json +++ b/.publish/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/compiler — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/core/package.json b/.publish/core/package.json index da8cb0dc..34b64c65 100644 --- a/.publish/core/package.json +++ b/.publish/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/core — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/csr/package.json b/.publish/csr/package.json index a425af47..7b777dca 100644 --- a/.publish/csr/package.json +++ b/.publish/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/csr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.45" + "@wrnexus/core": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/db/package.json b/.publish/db/package.json index c5b2be21..9dd076ab 100644 --- a/.publish/db/package.json +++ b/.publish/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/db — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/dev-server/package.json b/.publish/dev-server/package.json index 88ab3dca..971b6e65 100644 --- a/.publish/dev-server/package.json +++ b/.publish/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/dev-server — part of the WrNexus framework.", "license": "MIT", @@ -25,18 +25,18 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.45", - "@wrnexus/router": "^0.2.45", - "@wrnexus/ssr": "^0.2.45", - "@wrnexus/csr": "^0.2.45", - "@wrnexus/compiler": "^0.2.45", - "@wrnexus/styles": "^0.2.45", - "@wrnexus/ui": "^0.2.45", - "@wrnexus/validation": "^0.2.45", - "@wrnexus/i18n": "^0.2.45", - "@wrnexus/db": "^0.2.45", - "@wrnexus/pubsub": "^0.2.45", - "@wrnexus/uploader": "^0.2.45" + "@wrnexus/core": "^0.2.46", + "@wrnexus/router": "^0.2.46", + "@wrnexus/ssr": "^0.2.46", + "@wrnexus/csr": "^0.2.46", + "@wrnexus/compiler": "^0.2.46", + "@wrnexus/styles": "^0.2.46", + "@wrnexus/ui": "^0.2.46", + "@wrnexus/validation": "^0.2.46", + "@wrnexus/i18n": "^0.2.46", + "@wrnexus/db": "^0.2.46", + "@wrnexus/pubsub": "^0.2.46", + "@wrnexus/uploader": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/encryption/package.json b/.publish/encryption/package.json index da0a8133..623256ec 100644 --- a/.publish/encryption/package.json +++ b/.publish/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/encryption — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/helpers/package.json b/.publish/helpers/package.json index da9da07f..ec698b92 100644 --- a/.publish/helpers/package.json +++ b/.publish/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.45" + "@wrnexus/core": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/i18n/package.json b/.publish/i18n/package.json index 65785b3a..baeab7df 100644 --- a/.publish/i18n/package.json +++ b/.publish/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/i18n — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.45" + "@wrnexus/core": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/jwt/package.json b/.publish/jwt/package.json index ce9aa3cc..8bfb7bd0 100644 --- a/.publish/jwt/package.json +++ b/.publish/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/jwt — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/mobile/package.json b/.publish/mobile/package.json index c12e8ddf..acb52552 100644 --- a/.publish/mobile/package.json +++ b/.publish/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/mobile — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/native": "^0.2.45" + "@wrnexus/native": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/native/package.json b/.publish/native/package.json index 896ed00c..fe43e3f9 100644 --- a/.publish/native/package.json +++ b/.publish/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/native — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/oauth/package.json b/.publish/oauth/package.json index c0496ec4..8e3f9138 100644 --- a/.publish/oauth/package.json +++ b/.publish/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/oauth — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/pubsub/package.json b/.publish/pubsub/package.json index 7269163d..be2e2da7 100644 --- a/.publish/pubsub/package.json +++ b/.publish/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/pubsub — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/queue/package.json b/.publish/queue/package.json index 7cc6bf49..2be83493 100644 --- a/.publish/queue/package.json +++ b/.publish/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/queue — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/reactive/package.json b/.publish/reactive/package.json index 2d78c021..a94ea833 100644 --- a/.publish/reactive/package.json +++ b/.publish/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/reactive — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/router/package.json b/.publish/router/package.json index 4f52efd0..18c9b238 100644 --- a/.publish/router/package.json +++ b/.publish/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/router — part of the WrNexus framework.", "license": "MIT", @@ -21,8 +21,8 @@ } }, "dependencies": { - "@wrnexus/compiler": "^0.2.45", - "@wrnexus/core": "^0.2.45" + "@wrnexus/compiler": "^0.2.46", + "@wrnexus/core": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/ssr/package.json b/.publish/ssr/package.json index 2b5d23d4..e0ef718f 100644 --- a/.publish/ssr/package.json +++ b/.publish/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/ssr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.45" + "@wrnexus/core": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/styles/package.json b/.publish/styles/package.json index 272b3498..897e838f 100644 --- a/.publish/styles/package.json +++ b/.publish/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/styles — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/uploader": "^0.2.45" + "@wrnexus/uploader": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/test/package.json b/.publish/test/package.json index 7858e971..77e17826 100644 --- a/.publish/test/package.json +++ b/.publish/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/test — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/tracking/package.json b/.publish/tracking/package.json index b4d472e7..3279c6a8 100644 --- a/.publish/tracking/package.json +++ b/.publish/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/tracking — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/ui/package.json b/.publish/ui/package.json index 5d36a943..3c5bd4ba 100644 --- a/.publish/ui/package.json +++ b/.publish/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/ui — part of the WrNexus framework.", "license": "MIT", @@ -22,7 +22,7 @@ "./ui.css": "./ui.css" }, "dependencies": { - "@wrnexus/core": "^0.2.45" + "@wrnexus/core": "^0.2.46" }, "files": [ "dist", diff --git a/.publish/uploader/package.json b/.publish/uploader/package.json index adeba5c4..ac4f47e4 100644 --- a/.publish/uploader/package.json +++ b/.publish/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/uploader — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.45" + "@wrnexus/core": "^0.2.46" }, "files": [ "dist" diff --git a/.publish/validation/package.json b/.publish/validation/package.json index ca2918a2..a8822d55 100644 --- a/.publish/validation/package.json +++ b/.publish/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "description": "@wrnexus/validation — part of the WrNexus framework.", "license": "MIT", diff --git a/packages/ai/package.json b/packages/ai/package.json index c4905d3c..4f2216d1 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", diff --git a/packages/authz/package.json b/packages/authz/package.json index 7e0920cc..69fe5f64 100644 --- a/packages/authz/package.json +++ b/packages/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/cli/package.json b/packages/cli/package.json index 3ca6f19f..637b9a14 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index ce15cbf1..983a0a64 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -483,6 +483,15 @@ const MIGRATIONS: Migration[] = [ // No generated application files require automatic migration. }, }, + { + version: "0.2.46", + id: "conditions-bug", + description: + "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", + apply() { + // No generated application files require automatic migration. + }, + }, ]; /** Release tooling uses this to require an explicit migration entry per version. */ diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 52ba1e9c..480bfc01 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index e78119e3..0a3c18a2 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -1303,7 +1303,9 @@ function generateComponent(ast: PageAst): string { const scopeLine = needsScope && scopeKeys.length > 0 - ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys.map((k) => `${JSON.stringify(k)}: ${nameRefs.get(k)}`).join(", ")} });\n` + ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys + .map((key) => `${JSON.stringify(key)}: ${nameRefs.get(key)}`) + .join(", ")} });\n` : needsScope ? ` const __scope = "";\n` : ""; @@ -1419,19 +1421,56 @@ function __wireRaw(v: any): string { }`); if (needsScope) { - out.push(`function __wrnexusScopeDecl(obj: Record): string { - const lit = (v: any) => - typeof v === "number" || typeof v === "boolean" - ? String(v) - : "'" + String(v).replace(/\\\\/g, "\\\\\\\\").replace(/'/g, "\\\\'").replace(/\\n/g, "\\\\n") + "'"; - return Object.keys(obj) - .map((k) => k + ": " + lit(obj[k])) - .join(", ") - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); -}`); + out.push(`function __wrnexusSerializeScopeValue(value: any): string { + if (value === undefined) { + return "undefined"; + } + + if (value === null) { + return "null"; + } + + if (typeof value === "number") { + return Number.isFinite(value) + ? String(value) + : "null"; + } + + if (typeof value === "boolean") { + return value ? "true" : "false"; + } + + if (typeof value === "string") { + return JSON.stringify(value); + } + + try { + const serialized = JSON.stringify(value); + + return serialized === undefined + ? "undefined" + : serialized; + } catch { + return "null"; + } + } + + function __wrnexusScopeDecl(obj: Record): string { + return Object.keys(obj) + .map( + (key) => + key + + ": " + + __wrnexusSerializeScopeValue( + obj[key], + ), + ) + .join(", ") + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); + }`); } const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : ""; diff --git a/packages/compiler/test/compiler.test.ts b/packages/compiler/test/compiler.test.ts index 1d3b7f4e..86dcf225 100644 --- a/packages/compiler/test/compiler.test.ts +++ b/packages/compiler/test/compiler.test.ts @@ -98,10 +98,18 @@ test("stateful component: state text baked into a reactive data-text span, prop ); const render = mod.render as (p: Record) => string; const out = render({ start: "10", label: "Score" }); - expect(out).toContain("data-scope=\"start: 10, label: 'Score', count: 10\""); + expect(out).toContain('data-scope="start: 10, label: "Score", count: 10"'); expect(out).toContain('data-on-click="count++"'); // label baked as static text; count baked as its initial value AND kept live. expect(out).toContain('Score: 10'); + + expect(out).toContain('data-scope="'); + + expect(out).toContain("start: 10"); + + expect(out).toContain("label: "Score""); + + expect(out).toContain("count: 10"); }); test("prop type coercion follows the default value's type", async () => { diff --git a/packages/core/package.json b/packages/core/package.json index 2f86541d..c438db58 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/package.json b/packages/csr/package.json index dc6ad0c5..09206d4f 100644 --- a/packages/csr/package.json +++ b/packages/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/src/reactive-runtime.ts b/packages/csr/src/reactive-runtime.ts index eb8f22b7..dc5130bf 100644 --- a/packages/csr/src/reactive-runtime.ts +++ b/packages/csr/src/reactive-runtime.ts @@ -43,6 +43,34 @@ export const REACTIVE_RUNTIME = String.raw` }); } + function decodeScopePayload(value) { + var binary = window.atob(value); + var bytes = + new Uint8Array(binary.length); + + for ( + var index = 0; + index < binary.length; + index++ + ) { + bytes[index] = + binary.charCodeAt(index); + } + + var decoded = + new TextDecoder( + "utf-8", + ).decode(bytes); + + var parsed = + JSON.parse(decoded); + + return parsed && + typeof parsed === "object" + ? parsed + : {}; + } + function decodeBehaviorPayload(value) { var binary = window.atob(value); var bytes = new Uint8Array(binary.length); @@ -310,11 +338,43 @@ export const REACTIVE_RUNTIME = String.raw` } function setupScope(el) { - if (el.__wrnexusScope) return; // idempotent: safe to call again after an HMR morph + if (el.__wrnexusScope) return; + el.__wrnexusScope = true; - el.__wrnexusHydrated = true; // marks the subtree as client-owned for the HMR morph - var decl = el.getAttribute("data-scope") || ""; - var initial = parseScopeDecl(decl); + el.__wrnexusHydrated = true; + + var encodedScope = + el.getAttribute( + "data-wrn-scope", + ); + + var initial; + + if (encodedScope) { + try { + initial = + decodeScopePayload( + encodedScope, + ); + } catch (error) { + console.error( + "[wrnexus] failed to decode scope payload", + error, + ); + + initial = {}; + } + } else { + var declaration = + el.getAttribute( + "data-scope", + ) || ""; + + initial = + parseScopeDecl( + declaration, + ); + } var signals = {}; Object.keys(initial).forEach(function (k) { signals[k] = signal(initial[k]); }); @@ -374,11 +434,17 @@ export const REACTIVE_RUNTIME = String.raw` binary.charCodeAt(index); } - return JSON.parse( - new TextDecoder( - "utf-8", - ).decode(bytes), - ); + var value = + JSON.parse( + new TextDecoder( + "utf-8", + ).decode(bytes), + ); + + return value && + typeof value === "object" + ? value + : {}; } catch (error) { console.error( "[wrnexus] failed to decode loop locals", @@ -572,7 +638,13 @@ export const REACTIVE_RUNTIME = String.raw` // so an outer scope never clobbers an inner one's values. function owns(node) { var host = node.nodeType === 1 ? node : node.parentNode; - return !!host && host.closest && host.closest("[data-scope]") === el; + return ( + !!host && + host.closest && + host.closest( + "[data-scope], [data-wrn-scope]", + ) === el + ); } // --- data-for list rendering ------------------------------------------- @@ -925,34 +997,128 @@ export const REACTIVE_RUNTIME = String.raw` } } - Array.prototype.slice.call(el.querySelectorAll("[data-for]")).forEach(function (tpl) { - if (!tpl.parentNode || !owns(tpl)) return; - var spec = parseFor(tpl.getAttribute("data-for")); - if (!spec) return; - tpl.removeAttribute("data-for"); - var parent = tpl.parentNode; - var marker = document.createComment("wire-for"); - parent.insertBefore(marker, tpl); + Array.prototype.slice + .call( + el.querySelectorAll( + "[data-for]", + ), + ) + .forEach(function (tpl) { + if ( + !tpl.parentNode || + !owns(tpl) + ) { + return; + } + + var spec = + parseFor( + tpl.getAttribute( + "data-for", + ), + ); + + if (!spec) { + return; + } + + var template = + tpl.cloneNode(true); + + template.removeAttribute( + "data-for", + ); + + var parent = + tpl.parentNode; + + var marker = + document.createComment( + "wrn-for", + ); + + parent.insertBefore( + marker, + tpl, + ); + parent.removeChild(tpl); + var clones = []; + reactive(function () { - var list = evalExpr(spec.list); - if (!list || typeof list.length !== "number") list = []; - for (var c = 0; c < clones.length; c++) { - if (clones[c].parentNode) clones[c].parentNode.removeChild(clones[c]); + var list = + evalExpr(spec.list); + + if (!Array.isArray(list)) { + console.error( + "[wrnexus] data-for expected an array for '" + + spec.list + + "', received", + list, + ); + + list = []; } + + for ( + var cloneIndex = 0; + cloneIndex < + clones.length; + cloneIndex++ + ) { + var existingClone = + clones[cloneIndex]; + + if ( + existingClone.parentNode + ) { + existingClone.parentNode + .removeChild( + existingClone, + ); + } + } + clones = []; - var frag = document.createDocumentFragment(); - for (var i = 0; i < list.length; i++) { - var clone = tpl.cloneNode(true); + + var fragment = + document.createDocumentFragment(); + + for ( + var itemIndex = 0; + itemIndex < list.length; + itemIndex++ + ) { + var clone = + template.cloneNode(true); + var locals = {}; - locals[spec.item] = list[i]; - if (spec.index) locals[spec.index] = i; - hydrateItem(clone, locals); - frag.appendChild(clone); + + locals[spec.item] = + list[itemIndex]; + + if (spec.index) { + locals[spec.index] = + itemIndex; + } + + hydrateItem( + clone, + locals, + ); + + fragment.appendChild( + clone, + ); + clones.push(clone); } - parent.insertBefore(frag, marker.nextSibling); + + parent.insertBefore( + fragment, + marker.nextSibling, + ); }); }); @@ -1009,11 +1175,14 @@ export const REACTIVE_RUNTIME = String.raw` expression, decodeLoopLocals(node), ); - } catch (e) { + } catch (error) { enabled = false; } - node.classList.toggle(className, enabled); + node.classList.toggle( + className, + enabled, + ); }); }); }); @@ -1076,7 +1245,7 @@ export const REACTIVE_RUNTIME = String.raw` try { raw = evalExpr( exact[1].trim(), - locals, + decodeLoopLocals(node), ); } catch (error) { return; @@ -1090,10 +1259,10 @@ export const REACTIVE_RUNTIME = String.raw` ) { try { var value = - evalExpr( - expression.trim(), - locals, - ); + evalExpr( + expression.trim(), + decodeLoopLocals(node), + ); return value == null ? "" @@ -1125,7 +1294,18 @@ export const REACTIVE_RUNTIME = String.raw` reactive(function () { node.nodeValue = tpl.replace(/\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g, function (_, doubleExpr, singleExpr) { var expr = doubleExpr || singleExpr; - try { return String(evalExpr(expr.trim())); } catch (err) { return ""; } + try { + var value = evalExpr( + expr.trim(), + decodeLoopLocals( + node.parentNode, + ), + ); + + return value == null + ? "" + : String(value); + } catch (err) { return ""; } }); }); })(textNode, template); @@ -1152,16 +1332,24 @@ export const REACTIVE_RUNTIME = String.raw` var stmt = attr.value; var listener = function (event) { - try { - var locals = - decodeLoopLocals(node); + var locals = + decodeLoopLocals(node); - locals.event = event; - locals.$event = event; + locals.event = event; + locals.$event = event; - runStmt(stmt, locals); + try { + runStmt( + stmt, + locals, + ); } catch (error) { - console.error("[wrnexus] handler error in '" + stmt + "'", error); + console.error( + "[wrnexus] handler error in '" + + stmt + + "'", + error, + ); } }; @@ -1276,8 +1464,24 @@ export const REACTIVE_RUNTIME = String.raw` function hydrateScopes(root) { var host = root || document; - if (host.nodeType === 1 && host.matches && host.matches("[data-scope]")) setupScope(host); - if (host.querySelectorAll) host.querySelectorAll("[data-scope]").forEach(setupScope); + + var scopeSelector = + "[data-scope], [data-wrn-scope]"; + + if ( + host.nodeType === 1 && + host.matches && + host.matches(scopeSelector) + ) { + setupScope(host); + } + + if (host.querySelectorAll) { + host + .querySelectorAll(scopeSelector) + .forEach(setupScope); + } + ensureBehaviorObserver(); } diff --git a/packages/csr/test/reactive.test.ts b/packages/csr/test/reactive.test.ts index 330d198c..6fdfc9b6 100644 --- a/packages/csr/test/reactive.test.ts +++ b/packages/csr/test/reactive.test.ts @@ -1,6 +1,7 @@ import { test, expect, beforeEach } from "bun:test"; import { Window } from "happy-dom"; import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts"; +import { mountHtml } from "@wrnexus/test"; // Fresh DOM per test, with the runtime's globals bound. function mount(html: string): Window { @@ -164,3 +165,48 @@ test("independent signals in one scope update correctly (dependency tracking)", click("bb"); expect([ta(), tb()]).toEqual(["2", "101"]); }); +test("data-for preserves arrays of objects from encoded component scope", () => { + const items = [ + { + question: "One", + answer: "First", + }, + { + question: "Two", + answer: "Second", + }, + { + question: "Three", + answer: "Third", + }, + { + question: "Four", + answer: "Fourth", + }, + ]; + + const encoded = Buffer.from( + JSON.stringify({ + items, + }), + "utf8", + ).toString("base64"); + + const dom = mountHtml(` +
+
+ {index + 1} + {item.question} +
+
+ `); + + expect(dom.querySelectorAll("article")).toHaveLength(4); + + expect(dom.querySelectorAll("strong").map((element) => element.textContent)).toEqual([ + "One", + "Two", + "Three", + "Four", + ]); +}); diff --git a/packages/db/package.json b/packages/db/package.json index 80fdfb8c..6a6b8c99 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index b7d98be4..25454ff7 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 9ccfb3a5..2aa496dd 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 5e1d7407..788543d6 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 40c93bc8..4aa472f8 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/jwt/package.json b/packages/jwt/package.json index 1ef2e274..5586144d 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 5f0a36ba..ccd1378c 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/native/package.json b/packages/native/package.json index b366b8d7..9aaffd15 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/oauth/package.json b/packages/oauth/package.json index 49e5f0b1..01e27691 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index 0fc033ee..5f199482 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/queue/package.json b/packages/queue/package.json index f90066c1..f8d02842 100644 --- a/packages/queue/package.json +++ b/packages/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/reactive/package.json b/packages/reactive/package.json index 0e004123..b461579f 100644 --- a/packages/reactive/package.json +++ b/packages/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/router/package.json b/packages/router/package.json index e196a498..6b4fe64c 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/ssr/package.json b/packages/ssr/package.json index 19ce1104..31753bc4 100644 --- a/packages/ssr/package.json +++ b/packages/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/styles/package.json b/packages/styles/package.json index b7f532ff..2eaf8b95 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.2.45", + "version": "0.2.46", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/test/package.json b/packages/test/package.json index 8be81288..67fc0bf5 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/tracking/package.json b/packages/tracking/package.json index 720f4fe1..9450f8e9 100644 --- a/packages/tracking/package.json +++ b/packages/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index ab220cab..9b58a874 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/uploader/package.json b/packages/uploader/package.json index 3c277faa..52993abb 100644 --- a/packages/uploader/package.json +++ b/packages/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/validation/package.json b/packages/validation/package.json index e1c75aaa..3248a202 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.2.45", + "version": "0.2.46", "private": true, "type": "module", "main": "src/index.ts", diff --git a/update-package-versions.mjs b/update-package-versions.mjs index 315a7290..0de494de 100644 --- a/update-package-versions.mjs +++ b/update-package-versions.mjs @@ -1,7 +1,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -const version = "0.2.45"; +const version = "0.2.46"; const dependencyGroups = [ "dependencies",