From 1c2b2c532bad5bf932b165739f117a3ca0b550d7 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 15 Apr 2026 02:30:05 +0100 Subject: [PATCH] feat(html): recurse attr through layout trees Co-Authored-By: Virgil --- docs/architecture.md | 2 +- node.go | 16 +++++++++++++++- node_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 7f247e9..7018240 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -22,7 +22,7 @@ All concrete node types are unexported structs with exported constructor functio | Constructor | Behaviour | |-------------|-----------| | `El(tag, ...Node)` | HTML element with children. Void elements (`br`, `img`, `input`, etc.) never emit a closing tag. | -| `Attr(Node, key, value)` | Sets an attribute on an `El` node. Traverses through `If`, `Unless`, `Entitled`, `Each`, `EachSeq`, and `Switch` wrappers. Returns the node for chaining. | +| `Attr(Node, key, value)` | Sets an attribute on an `El` node. Traverses through `If`, `Unless`, `Entitled`, `Each`, `EachSeq`, `Switch`, `Layout`, and `Responsive` wrappers. Returns the node for chaining. | | `AriaLabel(Node, label)` | Convenience helper that sets `aria-label` on an element node. | | `AltText(Node, text)` | Convenience helper that sets `alt` on an element node. | | `TabIndex(Node, index)` | Convenience helper that sets `tabindex` on an element node. | diff --git a/node.go b/node.go index d0944f4..0956fe3 100644 --- a/node.go +++ b/node.go @@ -95,7 +95,8 @@ func El(tag string, children ...Node) Node { // Attr sets an attribute on an El node. Returns the node for chaining. // Usage example: Attr(El("a", Text("docs")), "href", "/docs") -// It recursively traverses through wrappers like If, Unless, Entitled, and Each. +// It recursively traverses through wrappers like If, Unless, Entitled, Each, +// Layout, and Responsive when present. func Attr(n Node, key, value string) Node { if n == nil { return n @@ -114,6 +115,19 @@ func Attr(n Node, key, value string) Node { for _, child := range t.cases { Attr(child, key, value) } + case *Layout: + if t.slots != nil { + for slot, children := range t.slots { + for i := range children { + children[i] = Attr(children[i], key, value) + } + t.slots[slot] = children + } + } + case *Responsive: + for i := range t.variants { + Attr(t.variants[i].layout, key, value) + } case attrApplier: t.applyAttr(key, value) } diff --git a/node_test.go b/node_test.go index 117bc77..fa1bb07 100644 --- a/node_test.go +++ b/node_test.go @@ -481,6 +481,30 @@ func TestAttr_ThroughSwitchNode_Good(t *testing.T) { } } +func TestAttr_ThroughLayout_Good(t *testing.T) { + ctx := NewContext() + layout := NewLayout("C").C(El("div", Raw("content"))) + Attr(layout, "class", "page") + + got := layout.Render(ctx) + want := `
content
` + if got != want { + t.Errorf("Attr through Layout = %q, want %q", got, want) + } +} + +func TestAttr_ThroughResponsive_Good(t *testing.T) { + ctx := NewContext() + resp := NewResponsive().Variant("mobile", NewLayout("C").C(El("div", Raw("content")))) + Attr(resp, "data-kind", "page") + + got := resp.Render(ctx) + want := `
content
` + if got != want { + t.Errorf("Attr through Responsive = %q, want %q", got, want) + } +} + func TestAttr_ThroughEachNode_Good(t *testing.T) { ctx := NewContext() node := Each([]string{"a", "b"}, func(item string) Node {