fix(compiler): preserve native boolean semantics in page branches
This commit is contained in:
1 parent
681679483a
commit
25a421c727
4 files changed
+38
-2
No files matched your search
@@ -320,7 +320,7 @@
|
|||||||
},
|
},
|
||||||
"packages/compiler": {
|
"packages/compiler": {
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.25",
|
"version": "0.8.26",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/csr": "workspace:*",
|
"@wrnexus/csr": "workspace:*",
|
||||||
"@wrnexus/store": "workspace:*",
|
"@wrnexus/store": "workspace:*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.25",
|
"version": "0.8.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -511,6 +511,19 @@ function renderLoopBody(node: ViewNode, locals: string[] = []): string {
|
|||||||
return escLit(` ${name}`);
|
return escLit(` ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Native boolean attributes inside #if/#each must use presence semantics,
|
||||||
|
// just like attributes on the page root. disabled="false" still disables
|
||||||
|
// an HTML input. Component props retain their serialized boolean value.
|
||||||
|
if (
|
||||||
|
!componentTag &&
|
||||||
|
!node.attrs.some((a) => a.name === "data-component") &&
|
||||||
|
isHtmlBooleanAttribute(name)
|
||||||
|
) {
|
||||||
|
const expression = wholeAttributeExpression(attr.value);
|
||||||
|
if (expression) return "${(" + expression + ") ? " + JSON.stringify(` ${name}`) + ' : ""}';
|
||||||
|
if (attr.value === "false") return "";
|
||||||
|
}
|
||||||
|
|
||||||
return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`);
|
return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`);
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import { parse, generate } from "../src/index.ts";
|
||||||
|
|
||||||
|
test("native boolean attributes inside conditional and loop bodies use presence semantics", async () => {
|
||||||
|
const source = `page Example {
|
||||||
|
load server account { return {} }
|
||||||
|
view {
|
||||||
|
{#if account.visible}<input required disabled="{!account.edit}" />{/if}
|
||||||
|
{#each account.rows as row}<input type="checkbox" checked="{row.checked}" disabled="false" />{/each}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(generate(parse(source)));
|
||||||
|
const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
|
||||||
|
const html = await mod.default({
|
||||||
|
account: { visible: true, edit: true, rows: [{ checked: false }, { checked: true }] },
|
||||||
|
});
|
||||||
|
expect(html).not.toContain("disabled");
|
||||||
|
expect(html).not.toContain('checked="false"');
|
||||||
|
expect(html.match(/ checked/g)).toHaveLength(1);
|
||||||
|
expect(html).toContain("required");
|
||||||
|
const denied = await mod.default({ account: { visible: true, edit: false, rows: [] } });
|
||||||
|
expect(denied).toContain(" disabled");
|
||||||
|
});
|
||||||
Reference in new issue
Block a user