Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
973d580bc5 | ||
|
|
8a8328a308 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.44",
|
||||
"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.44",
|
||||
"@wrnexus/router": "^0.2.44",
|
||||
"@wrnexus/csr": "^0.2.44",
|
||||
"@wrnexus/compiler": "^0.2.44",
|
||||
"@wrnexus/styles": "^0.2.44",
|
||||
"@wrnexus/dev-server": "^0.2.44",
|
||||
"@wrnexus/ui": "^0.2.44",
|
||||
"@wrnexus/validation": "^0.2.44",
|
||||
"@wrnexus/i18n": "^0.2.44",
|
||||
"@wrnexus/db": "^0.2.44"
|
||||
"@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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.44",
|
||||
"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.44",
|
||||
"@wrnexus/router": "^0.2.44",
|
||||
"@wrnexus/ssr": "^0.2.44",
|
||||
"@wrnexus/csr": "^0.2.44",
|
||||
"@wrnexus/compiler": "^0.2.44",
|
||||
"@wrnexus/styles": "^0.2.44",
|
||||
"@wrnexus/ui": "^0.2.44",
|
||||
"@wrnexus/validation": "^0.2.44",
|
||||
"@wrnexus/i18n": "^0.2.44",
|
||||
"@wrnexus/db": "^0.2.44",
|
||||
"@wrnexus/pubsub": "^0.2.44",
|
||||
"@wrnexus/uploader": "^0.2.44"
|
||||
"@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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/native": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.44",
|
||||
"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.44",
|
||||
"@wrnexus/core": "^0.2.44"
|
||||
"@wrnexus/compiler": "^0.2.46",
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/uploader": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.44",
|
||||
"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.44"
|
||||
"@wrnexus/core": "^0.2.46"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -474,6 +474,24 @@ const MIGRATIONS: Migration[] = [
|
||||
// No generated application files require automatic migration.
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.2.45",
|
||||
id: "conditions-bug",
|
||||
description:
|
||||
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||
apply() {
|
||||
// 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. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -524,7 +524,13 @@ function hasClientBehavior(nodes: ViewNode[]): boolean {
|
||||
// `{t:key}` is i18n sugar resolved server-side — not client reactivity.
|
||||
if (node.type === "text") return /\{(?!t:)[^{}]+\}/.test(node.value);
|
||||
// Server control blocks render on the server; they don't add client reactivity.
|
||||
if (node.type === "each" || node.type === "if") return false;
|
||||
if (node.type === "each") {
|
||||
return hasClientBehavior(node.body) || hasClientBehavior(node.empty);
|
||||
}
|
||||
|
||||
if (node.type === "if") {
|
||||
return node.branches.some((branch) => hasClientBehavior(branch.body));
|
||||
}
|
||||
return (
|
||||
node.attrs.some((attr) => attr.event || attr.name === "csrGet" || attr.name === "csrText") ||
|
||||
hasClientBehavior(node.children)
|
||||
@@ -1112,6 +1118,16 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
return renderNestedComponentInvocation(node, ctx);
|
||||
}
|
||||
|
||||
const loopVariables = loopVarsOf(node);
|
||||
|
||||
const elementContext =
|
||||
loopVariables.length > 0
|
||||
? {
|
||||
...ctx,
|
||||
loopVars: new Set([...(ctx.loopVars ?? []), ...loopVariables]),
|
||||
}
|
||||
: ctx;
|
||||
|
||||
let bindIndex = 0;
|
||||
const staticClasses: string[] = [];
|
||||
const conditionalClasses: Array<{
|
||||
@@ -1143,9 +1159,15 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
return ` ${a.name}`;
|
||||
}
|
||||
|
||||
const rendered = ` ${a.name}="${compileAttrValue(a.value, ctx)}"`;
|
||||
const rendered = ` ${a.name}="${compileAttrValue(a.value, elementContext)}"`;
|
||||
|
||||
if (!a.value.includes("{") || !exprRefsState(a.value, ctx.stateNames)) {
|
||||
const referencesState = exprRefsState(a.value, ctx.stateNames);
|
||||
|
||||
const referencesLoopVariable = elementContext.loopVars
|
||||
? exprRefsState(a.value, elementContext.loopVars)
|
||||
: false;
|
||||
|
||||
if (!a.value.includes("{") || (!referencesState && !referencesLoopVariable)) {
|
||||
return rendered;
|
||||
}
|
||||
|
||||
@@ -1157,6 +1179,16 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
|
||||
const initialConditionalClasses = conditionalClasses
|
||||
.map(({ className, expression }) => {
|
||||
const referencesLoopVariable = elementContext.loopVars
|
||||
? exprRefsState(expression, elementContext.loopVars)
|
||||
: false;
|
||||
|
||||
// data-for variables do not exist during
|
||||
// initial server rendering.
|
||||
if (referencesLoopVariable) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
|
||||
})
|
||||
.join("");
|
||||
@@ -1168,7 +1200,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
|
||||
const classAttribute =
|
||||
staticClasses.length > 0 || conditionalClasses.length > 0
|
||||
? ` class="${compileAttrValue(staticClassValue, ctx)}${initialConditionalClasses}"`
|
||||
? ` class="${compileAttrValue(staticClassValue, elementContext)}${initialConditionalClasses}"`
|
||||
: "";
|
||||
|
||||
const classReactiveBinding = classHasReactiveExpression
|
||||
@@ -1186,11 +1218,6 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
})
|
||||
.join("");
|
||||
|
||||
// A `data-for` element introduces loop variables for its subtree.
|
||||
const loops = loopVarsOf(node);
|
||||
const childCtx =
|
||||
loops.length > 0 ? { ...ctx, loopVars: new Set([...(ctx.loopVars ?? []), ...loops]) } : ctx;
|
||||
|
||||
const allAttrs =
|
||||
`${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
|
||||
|
||||
@@ -1198,7 +1225,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
return `<${node.tag}${allAttrs}>`;
|
||||
}
|
||||
|
||||
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
|
||||
const inner = node.children.map((child) => renderComponentNode(child, elementContext)).join("");
|
||||
|
||||
return `<${node.tag}${allAttrs}>${inner}</${node.tag}>`;
|
||||
}
|
||||
@@ -1276,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`
|
||||
: "";
|
||||
@@ -1392,19 +1421,56 @@ function __wireRaw(v: any): string {
|
||||
}`);
|
||||
|
||||
if (needsScope) {
|
||||
out.push(`function __wrnexusScopeDecl(obj: Record<string, any>): 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, "<")
|
||||
.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, any>): string {
|
||||
return Object.keys(obj)
|
||||
.map(
|
||||
(key) =>
|
||||
key +
|
||||
": " +
|
||||
__wrnexusSerializeScopeValue(
|
||||
obj[key],
|
||||
),
|
||||
)
|
||||
.join(", ")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}`);
|
||||
}
|
||||
|
||||
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
|
||||
|
||||
@@ -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, string>) => 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: <span data-text="count">10</span>');
|
||||
|
||||
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 () => {
|
||||
@@ -839,3 +847,37 @@ component FAQAccordion {
|
||||
|
||||
expect(output).toContain("isOpen(index)");
|
||||
});
|
||||
test("data-for conditional classes do not evaluate loop variables during SSR", () => {
|
||||
const output = generate(
|
||||
parse(`
|
||||
component Accordion {
|
||||
props {
|
||||
items = []
|
||||
}
|
||||
|
||||
state openIndexes = []
|
||||
|
||||
functions {
|
||||
function isOpen(index) {
|
||||
return openIndexes.includes(index)
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<article
|
||||
data-for="item, index in items"
|
||||
class:open="isOpen(index)"
|
||||
>
|
||||
{item.title}
|
||||
</article>
|
||||
}
|
||||
}
|
||||
`),
|
||||
);
|
||||
|
||||
expect(output).toContain('data-for="item, index in items"');
|
||||
|
||||
expect(output).toContain("data-wrn-class-0");
|
||||
|
||||
expect(output).not.toContain('${(isOpen(index)) ? " open" : ""}');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -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]); });
|
||||
@@ -335,6 +395,65 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
return run;
|
||||
}
|
||||
function renderAll() { renderers.forEach(function (f) { f(); }); }
|
||||
function decodeLoopLocals(node) {
|
||||
var owner =
|
||||
node &&
|
||||
node.closest &&
|
||||
node.closest(
|
||||
"[data-wrn-loop-locals]",
|
||||
);
|
||||
|
||||
if (!owner) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var encoded =
|
||||
owner.getAttribute(
|
||||
"data-wrn-loop-locals",
|
||||
);
|
||||
|
||||
if (!encoded) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
var binary =
|
||||
window.atob(encoded);
|
||||
|
||||
var bytes =
|
||||
new Uint8Array(
|
||||
binary.length,
|
||||
);
|
||||
|
||||
for (
|
||||
var index = 0;
|
||||
index < binary.length;
|
||||
index++
|
||||
) {
|
||||
bytes[index] =
|
||||
binary.charCodeAt(index);
|
||||
}
|
||||
|
||||
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",
|
||||
error,
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
var behaviorFunctions = {};
|
||||
var stateWatchers = {};
|
||||
@@ -519,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 -------------------------------------------
|
||||
@@ -537,75 +662,463 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
try { return String(itemEval(e)); } catch (err) { return ""; }
|
||||
});
|
||||
}
|
||||
function hydrateItem(root, locals) {
|
||||
function hydrateItem(
|
||||
root,
|
||||
locals,
|
||||
) {
|
||||
function localRead(name) {
|
||||
return Object.prototype.hasOwnProperty.call(locals, name) ? locals[name] : peekScope(name);
|
||||
}
|
||||
function itemEval(expr) { return evaluateExpression(expr, localRead); }
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
locals,
|
||||
name,
|
||||
)
|
||||
) {
|
||||
return locals[name];
|
||||
}
|
||||
|
||||
var els = [root];
|
||||
if (root.querySelectorAll) Array.prototype.push.apply(els, root.querySelectorAll("*"));
|
||||
els.forEach(function (n) {
|
||||
if (n.nodeType !== 1) return;
|
||||
Array.prototype.slice.call(n.attributes).forEach(function (attr) {
|
||||
if (attr.name === "data-text") {
|
||||
try { n.textContent = String(itemEval(attr.value)); } catch (e) { /* ignore */ }
|
||||
} else if (attr.name.indexOf("data-on-") === 0) {
|
||||
var evt = attr.name.slice("data-on-".length);
|
||||
var stmt = attr.value;
|
||||
n.addEventListener(evt, function () {
|
||||
return readScope(name);
|
||||
}
|
||||
|
||||
function itemEval(expression) {
|
||||
return evaluateExpression(
|
||||
expression,
|
||||
localRead,
|
||||
);
|
||||
}
|
||||
|
||||
function applyItemTemplate(
|
||||
template,
|
||||
) {
|
||||
return String(template).replace(
|
||||
/\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g,
|
||||
function (
|
||||
_,
|
||||
doubleExpression,
|
||||
singleExpression,
|
||||
) {
|
||||
var expression = (
|
||||
doubleExpression ||
|
||||
singleExpression
|
||||
).trim();
|
||||
|
||||
try {
|
||||
var value =
|
||||
itemEval(expression);
|
||||
|
||||
return value == null
|
||||
? ""
|
||||
: String(value);
|
||||
} catch (_) {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
var elements = [root];
|
||||
|
||||
if (root.querySelectorAll) {
|
||||
Array.prototype.push.apply(
|
||||
elements,
|
||||
root.querySelectorAll("*"),
|
||||
);
|
||||
}
|
||||
|
||||
elements.forEach(function (node) {
|
||||
if (
|
||||
!node ||
|
||||
node.nodeType !== 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var attributes =
|
||||
Array.prototype.slice.call(
|
||||
node.attributes,
|
||||
);
|
||||
|
||||
attributes.forEach(function (
|
||||
attribute,
|
||||
) {
|
||||
/*
|
||||
* data-text must be handled using itemEval(),
|
||||
* because item/index only exist in loop locals.
|
||||
*/
|
||||
if (
|
||||
attribute.name === "data-text"
|
||||
) {
|
||||
try {
|
||||
var textValue =
|
||||
itemEval(attribute.value);
|
||||
|
||||
node.textContent =
|
||||
textValue == null
|
||||
? ""
|
||||
: String(textValue);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[wrnexus] data-for text binding failed for '" +
|
||||
attribute.value +
|
||||
"'",
|
||||
error,
|
||||
);
|
||||
|
||||
node.textContent = "";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Conditional classes:
|
||||
* data-wrn-class-*='["class","expression"]'
|
||||
*/
|
||||
if (
|
||||
attribute.name.indexOf(
|
||||
"data-wrn-class-",
|
||||
) === 0
|
||||
) {
|
||||
var classBinding;
|
||||
|
||||
try {
|
||||
classBinding = JSON.parse(
|
||||
attribute.value,
|
||||
);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!classBinding ||
|
||||
classBinding.length !== 2
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var className =
|
||||
classBinding[0];
|
||||
|
||||
var classExpression =
|
||||
classBinding[1];
|
||||
|
||||
var classEnabled = false;
|
||||
|
||||
try {
|
||||
classEnabled = !!itemEval(
|
||||
classExpression,
|
||||
);
|
||||
} catch (_) {
|
||||
classEnabled = false;
|
||||
}
|
||||
|
||||
node.classList.toggle(
|
||||
className,
|
||||
classEnabled,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reactive ordinary attributes:
|
||||
* data-wrn-bind-*='["aria-expanded","{isOpen(index)}"]'
|
||||
*/
|
||||
if (
|
||||
attribute.name.indexOf(
|
||||
"data-wrn-bind-",
|
||||
) === 0
|
||||
) {
|
||||
var binding;
|
||||
|
||||
try {
|
||||
binding = JSON.parse(
|
||||
attribute.value,
|
||||
);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!binding ||
|
||||
binding.length !== 2
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var attributeName =
|
||||
binding[0];
|
||||
|
||||
var attributeTemplate =
|
||||
binding[1];
|
||||
|
||||
var exact =
|
||||
/^\{([^{}]+)\}$/.exec(
|
||||
attributeTemplate,
|
||||
);
|
||||
|
||||
var rawValue;
|
||||
|
||||
if (exact) {
|
||||
try {
|
||||
splitTopLevel(stmt, ";").forEach(function (part) {
|
||||
runStatement(part, itemEval, localRead, function (name, value) {
|
||||
if (Object.prototype.hasOwnProperty.call(locals, name)) locals[name] = value;
|
||||
else if (!signals[name]) { signals[name] = signal(value); renderAll(); }
|
||||
else signals[name].set(value);
|
||||
});
|
||||
rawValue = itemEval(
|
||||
exact[1].trim(),
|
||||
);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
rawValue =
|
||||
attributeTemplate.replace(
|
||||
/\{([^{}]+)\}/g,
|
||||
function (
|
||||
_,
|
||||
expression,
|
||||
) {
|
||||
try {
|
||||
var value =
|
||||
itemEval(
|
||||
expression.trim(),
|
||||
);
|
||||
|
||||
return value == null
|
||||
? ""
|
||||
: String(value);
|
||||
} catch (_) {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
applyReactiveAttribute(
|
||||
node,
|
||||
attributeName,
|
||||
rawValue,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handlers inside data-for.
|
||||
*/
|
||||
if (
|
||||
attribute.name.indexOf(
|
||||
"data-on-",
|
||||
) === 0
|
||||
) {
|
||||
var eventName =
|
||||
attribute.name.slice(
|
||||
"data-on-".length,
|
||||
);
|
||||
|
||||
var statement =
|
||||
attribute.value;
|
||||
|
||||
node.addEventListener(
|
||||
eventName,
|
||||
function (event) {
|
||||
var eventLocals = {};
|
||||
|
||||
Object.keys(
|
||||
locals,
|
||||
).forEach(function (key) {
|
||||
eventLocals[key] =
|
||||
locals[key];
|
||||
});
|
||||
} catch (e) { console.error("[wrnexus] data-for handler error", e); }
|
||||
});
|
||||
} else if (attr.value.indexOf("{") !== -1) {
|
||||
attr.value = fillMustache(attr.value, itemEval);
|
||||
|
||||
eventLocals.event = event;
|
||||
eventLocals.$event = event;
|
||||
|
||||
try {
|
||||
runStmt(
|
||||
statement,
|
||||
eventLocals,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[wrnexus] data-for handler error in '" +
|
||||
statement +
|
||||
"'",
|
||||
error,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Static attributes containing interpolation.
|
||||
*/
|
||||
if (
|
||||
attribute.value &&
|
||||
attribute.value.indexOf("{") !== -1
|
||||
) {
|
||||
node.setAttribute(
|
||||
attribute.name,
|
||||
applyItemTemplate(
|
||||
attribute.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
|
||||
var tn;
|
||||
while ((tn = walker.nextNode())) {
|
||||
if (tn.nodeValue.indexOf("{") === -1) continue;
|
||||
tn.nodeValue = fillMustache(tn.nodeValue, itemEval);
|
||||
/*
|
||||
* Text interpolation inside loop content:
|
||||
*
|
||||
* <span>{item.title}</span>
|
||||
*/
|
||||
var walker =
|
||||
document.createTreeWalker(
|
||||
root,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
null,
|
||||
);
|
||||
|
||||
var textNode;
|
||||
|
||||
while (
|
||||
(textNode = walker.nextNode())
|
||||
) {
|
||||
var template =
|
||||
textNode.nodeValue || "";
|
||||
|
||||
if (
|
||||
template.indexOf("{") === -1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
textNode.nodeValue =
|
||||
applyItemTemplate(template);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -658,12 +1171,18 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
var enabled = false;
|
||||
|
||||
try {
|
||||
enabled = !!evalExpr(expression);
|
||||
} catch (e) {
|
||||
enabled = !!evalExpr(
|
||||
expression,
|
||||
decodeLoopLocals(node),
|
||||
);
|
||||
} catch (error) {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
node.classList.toggle(className, enabled);
|
||||
node.classList.toggle(
|
||||
className,
|
||||
enabled,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -671,32 +1190,97 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
// Reactive ordinary attributes emitted by the compiler. Each marker stores
|
||||
// [attributeName, originalTemplate], preserving an SSR value while allowing
|
||||
// state changes to update type, aria-*, class, href, and other attributes.
|
||||
var bindNodes = [el].concat(Array.prototype.slice.call(el.querySelectorAll("*")));
|
||||
var bindNodes = [el].concat(
|
||||
Array.prototype.slice.call(
|
||||
el.querySelectorAll("*"),
|
||||
),
|
||||
);
|
||||
|
||||
bindNodes.forEach(function (node) {
|
||||
if (!owns(node)) return;
|
||||
Array.prototype.slice.call(node.attributes).forEach(function (marker) {
|
||||
if (marker.name.indexOf("data-wrn-bind-") !== 0) return;
|
||||
var binding;
|
||||
try { binding = JSON.parse(marker.value); } catch (e) { return; }
|
||||
if (!binding || binding.length !== 2) return;
|
||||
var name = binding[0];
|
||||
var template = binding[1];
|
||||
reactive(function () {
|
||||
var exact = /^\{([^{}]+)\}$/.exec(template);
|
||||
var raw;
|
||||
if (exact) {
|
||||
try { raw = evalExpr(exact[1].trim()); } catch (e) { return; }
|
||||
} else {
|
||||
raw = template.replace(/\{([^{}]+)\}/g, function (_, expr) {
|
||||
try {
|
||||
var value = evalExpr(expr.trim());
|
||||
return value == null ? "" : String(value);
|
||||
} catch (e) { return ""; }
|
||||
});
|
||||
|
||||
Array.prototype.slice
|
||||
.call(node.attributes)
|
||||
.forEach(function (marker) {
|
||||
if (
|
||||
marker.name.indexOf(
|
||||
"data-wrn-bind-",
|
||||
) !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
applyReactiveAttribute(node, name, raw);
|
||||
|
||||
var binding;
|
||||
|
||||
try {
|
||||
binding = JSON.parse(
|
||||
marker.value,
|
||||
);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!binding ||
|
||||
binding.length !== 2
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = binding[0];
|
||||
var template = binding[1];
|
||||
|
||||
reactive(function () {
|
||||
var locals =
|
||||
decodeLoopLocals(node);
|
||||
|
||||
var exact =
|
||||
/^\{([^{}]+)\}$/.exec(
|
||||
template,
|
||||
);
|
||||
|
||||
var raw;
|
||||
|
||||
if (exact) {
|
||||
try {
|
||||
raw = evalExpr(
|
||||
exact[1].trim(),
|
||||
decodeLoopLocals(node),
|
||||
);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
raw = template.replace(
|
||||
/\{([^{}]+)\}/g,
|
||||
function (
|
||||
_,
|
||||
expression,
|
||||
) {
|
||||
try {
|
||||
var value =
|
||||
evalExpr(
|
||||
expression.trim(),
|
||||
decodeLoopLocals(node),
|
||||
);
|
||||
|
||||
return value == null
|
||||
? ""
|
||||
: String(value);
|
||||
} catch (error) {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
applyReactiveAttribute(
|
||||
node,
|
||||
name,
|
||||
raw,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// {{expr}} / {expr} interpolation in text nodes
|
||||
@@ -710,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);
|
||||
@@ -737,8 +1332,24 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
|
||||
var stmt = attr.value;
|
||||
var listener = function (event) {
|
||||
try { runStmt(stmt, { event: event, $event: event }); } catch (error) {
|
||||
console.error("[wrnexus] handler error in '" + stmt + "'", error);
|
||||
var locals =
|
||||
decodeLoopLocals(node);
|
||||
|
||||
locals.event = event;
|
||||
locals.$event = event;
|
||||
|
||||
try {
|
||||
runStmt(
|
||||
stmt,
|
||||
locals,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[wrnexus] handler error in '" +
|
||||
stmt +
|
||||
"'",
|
||||
error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -853,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(`
|
||||
<div data-wrn-scope="${encoded}">
|
||||
<article data-for="item, index in items">
|
||||
<span>{index + 1}</span>
|
||||
<strong>{item.question}</strong>
|
||||
</article>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(dom.querySelectorAll("article")).toHaveLength(4);
|
||||
|
||||
expect(dom.querySelectorAll("strong").map((element) => element.textContent)).toEqual([
|
||||
"One",
|
||||
"Two",
|
||||
"Three",
|
||||
"Four",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.44",
|
||||
"version": "0.2.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const version = "0.2.44";
|
||||
const version = "0.2.46";
|
||||
|
||||
const dependencyGroups = [
|
||||
"dependencies",
|
||||
|
||||
Reference in New Issue
Block a user