fix: merge multiple class attributes into single attribute
- codegen.zig: collect class values and output as single merged attribute - template.zig: respect quoted flag to prevent data lookup for static class values - Added tests for multiple class merging scenarios
This commit is contained in:
@@ -354,19 +354,31 @@ pub const Compiler = struct {
|
||||
}
|
||||
|
||||
fn visitAttributes(self: *Compiler, tag: *Node) CompilerError!void {
|
||||
// Collect class values to merge them into a single attribute
|
||||
var class_values = std.ArrayListUnmanaged([]const u8){};
|
||||
defer class_values.deinit(self.allocator);
|
||||
|
||||
// First pass: collect class values and output non-class attributes
|
||||
for (tag.attrs.items) |attr| {
|
||||
if (attr.val) |val| {
|
||||
// Skip empty class/style attributes
|
||||
if (mem.eql(u8, attr.name, "class") or mem.eql(u8, attr.name, "style")) {
|
||||
// Skip if value is empty, null, or undefined
|
||||
if (val.len == 0 or
|
||||
mem.eql(u8, val, "''") or
|
||||
mem.eql(u8, val, "\"\"") or
|
||||
mem.eql(u8, val, "null") or
|
||||
mem.eql(u8, val, "undefined"))
|
||||
{
|
||||
continue;
|
||||
// Check if value should be skipped (empty, null, undefined)
|
||||
const should_skip = val.len == 0 or
|
||||
mem.eql(u8, val, "''") or
|
||||
mem.eql(u8, val, "\"\"") or
|
||||
mem.eql(u8, val, "null") or
|
||||
mem.eql(u8, val, "undefined");
|
||||
|
||||
if (mem.eql(u8, attr.name, "class")) {
|
||||
// Collect class values to merge later
|
||||
if (!should_skip) {
|
||||
try class_values.append(self.allocator, val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty style attributes
|
||||
if (mem.eql(u8, attr.name, "style") and should_skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for boolean attributes in terse mode
|
||||
@@ -398,6 +410,19 @@ pub const Compiler = struct {
|
||||
try self.write(attr.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Output merged class attribute if any classes were collected
|
||||
if (class_values.items.len > 0) {
|
||||
try self.writeChar(' ');
|
||||
try self.write("class=\"");
|
||||
for (class_values.items, 0..) |class_val, i| {
|
||||
if (i > 0) {
|
||||
try self.writeChar(' ');
|
||||
}
|
||||
try self.write(class_val);
|
||||
}
|
||||
try self.writeChar('"');
|
||||
}
|
||||
}
|
||||
|
||||
fn visitText(self: *Compiler, text: *Node) CompilerError!void {
|
||||
|
||||
@@ -288,7 +288,11 @@ fn renderTag(allocator: Allocator, output: *std.ArrayListUnmanaged(u8), tag: *No
|
||||
substituteArgValue(attr.val, bindings)
|
||||
else
|
||||
attr.val;
|
||||
const attr_val = try evaluateAttrValue(allocator, final_val, data);
|
||||
// Static/quoted values (e.g., from .class shorthand) should not be looked up in data
|
||||
const attr_val = if (attr.quoted)
|
||||
runtime.AttrValue{ .string = final_val orelse "" }
|
||||
else
|
||||
try evaluateAttrValue(allocator, final_val, data);
|
||||
|
||||
// Collect class attributes for merging
|
||||
if (std.mem.eql(u8, attr.name, "class")) {
|
||||
@@ -685,7 +689,11 @@ fn renderTagWithItem(allocator: Allocator, output: *std.ArrayListUnmanaged(u8),
|
||||
|
||||
// Render attributes directly to output buffer (avoids intermediate allocations)
|
||||
for (tag.attrs.items) |attr| {
|
||||
const attr_val = try evaluateAttrValue(allocator, attr.val, data);
|
||||
// Static/quoted values (e.g., from .class shorthand) should not be looked up in data
|
||||
const attr_val = if (attr.quoted)
|
||||
runtime.AttrValue{ .string = attr.val orelse "" }
|
||||
else
|
||||
try evaluateAttrValue(allocator, attr.val, data);
|
||||
|
||||
// Collect class attributes for merging
|
||||
if (std.mem.eql(u8, attr.name, "class")) {
|
||||
|
||||
@@ -202,6 +202,18 @@ test "Implicit div with ID" {
|
||||
try expectOutput("#content", .{}, "<div id=\"content\"></div>");
|
||||
}
|
||||
|
||||
test "Multiple classes merged into single attribute" {
|
||||
try expectOutput("div.foo.bar.baz", .{}, "<div class=\"foo bar baz\"></div>");
|
||||
}
|
||||
|
||||
test "Multiple classes with text" {
|
||||
try expectOutput(".a.b hello", .{}, "<div class=\"a b\">hello</div>");
|
||||
}
|
||||
|
||||
test "Class attribute merged with shorthand classes" {
|
||||
try expectOutput("div(class=\"foo\").bar.baz", .{}, "<div class=\"foo bar baz\"></div>");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Test Case 10: &attributes spread operator
|
||||
// TODO: &attributes spread with JS object literal not yet implemented
|
||||
|
||||
Reference in New Issue
Block a user