feat: make state-based attributes reactive
This commit is contained in:
@@ -79,8 +79,35 @@ function eventAttribute(name: string): string {
|
||||
return `data-on-${name}`;
|
||||
}
|
||||
|
||||
function renderAttrs(attrs: Attr[], csrId?: string): string {
|
||||
const rendered = attrs.map(renderAttr).join("");
|
||||
function reactiveAttrValue(raw: string, reactive: PageReactive): string | null {
|
||||
let found = false;
|
||||
const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner: string) => {
|
||||
const expr = inner.trim();
|
||||
if (!exprRefsState(expr, reactive.stateNames)) return whole;
|
||||
found = true;
|
||||
try {
|
||||
const result = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
|
||||
return result == null ? "" : String(result);
|
||||
} catch {
|
||||
return whole;
|
||||
}
|
||||
});
|
||||
return found ? value : null;
|
||||
}
|
||||
|
||||
function renderAttrs(attrs: Attr[], csrId?: string, reactive: PageReactive | null = null): string {
|
||||
let bindIndex = 0;
|
||||
const rendered = attrs
|
||||
.map((attr) => {
|
||||
const base = renderAttr(attr);
|
||||
if (!reactive || attr.event || attr.boolean || !base || !attr.value.includes("{"))
|
||||
return base;
|
||||
const initial = reactiveAttrValue(attr.value, reactive);
|
||||
if (initial === null) return base;
|
||||
const marker = JSON.stringify([attr.name, attr.value]);
|
||||
return ` ${attr.name}="${attrEscape(initial)}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
|
||||
})
|
||||
.join("");
|
||||
return csrId ? `${rendered} data-wrnexus-csr="${attrEscape(csrId)}"` : rendered;
|
||||
}
|
||||
|
||||
@@ -312,7 +339,7 @@ function renderNode(
|
||||
|
||||
// Void elements (<br>, <img>, …) have no closing tag and no children.
|
||||
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
||||
return `<${node.tag}${renderAttrs(node.attrs, csrId)}>`;
|
||||
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>`;
|
||||
}
|
||||
|
||||
const inner =
|
||||
@@ -331,7 +358,7 @@ function renderNode(
|
||||
)
|
||||
.join("");
|
||||
|
||||
return `<${node.tag}${renderAttrs(node.attrs, csrId)}>${inner}</${node.tag}>`;
|
||||
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}</${node.tag}>`;
|
||||
}
|
||||
|
||||
function ssrMarker(bindings: SsrBinding[], binding: RenderBinding): string {
|
||||
@@ -745,14 +772,16 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
);
|
||||
}
|
||||
|
||||
let bindIndex = 0;
|
||||
const attrs = node.attrs
|
||||
.map((a) =>
|
||||
a.event
|
||||
? ` ${eventAttribute(a.name)}="${compileAttrValue(a.value, ctx)}"`
|
||||
: a.boolean
|
||||
? ` ${a.name}`
|
||||
: ` ${a.name}="${compileAttrValue(a.value, ctx)}"`,
|
||||
)
|
||||
.map((a) => {
|
||||
if (a.event) return ` ${eventAttribute(a.name)}="${compileAttrValue(a.value, ctx)}"`;
|
||||
if (a.boolean) return ` ${a.name}`;
|
||||
const rendered = ` ${a.name}="${compileAttrValue(a.value, ctx)}"`;
|
||||
if (!a.value.includes("{") || !exprRefsState(a.value, ctx.stateNames)) return rendered;
|
||||
const marker = attrEscape(JSON.stringify([a.name, a.value]));
|
||||
return `${rendered} data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
// A `data-for` element introduces loop variables for its subtree.
|
||||
|
||||
Reference in New Issue
Block a user