Merge remote-tracking branch 'upstream/main'
1 file changed, 79 insertions(+), 0 deletions(-)
changed files
M components/components.go → components/components.go
@@ -64,3 +64,82 @@ var b strings.Builder _ = c.Render(&b) return b.String() } + +// JoinAttrs with the given name only on the first level of the given nodes. +// This means that attributes on non-direct descendants are ignored. +// Attribute values are joined by spaces. +// Note that this renders all first-level attributes to check whether they should be processed. +func JoinAttrs(name string, children ...g.Node) g.Node { + var attrValues []string + var result []g.Node + firstAttrIndex := -1 + + // Process all children + for _, child := range children { + // Handle groups explicitly because they may contain attributes + if group, ok := child.(g.Group); ok { + for _, groupChild := range group { + isGivenAttr, attrValue := extractAttrValue(name, groupChild) + if !isGivenAttr || attrValue == "" { + result = append(result, groupChild) + continue + } + + attrValues = append(attrValues, attrValue) + if firstAttrIndex == -1 { + firstAttrIndex = len(result) + result = append(result, nil) + } + } + + continue + } + + // Handle non-group nodes essentially the same way + isGivenAttr, attrValue := extractAttrValue(name, child) + if !isGivenAttr || attrValue == "" { + result = append(result, child) + continue + } + + attrValues = append(attrValues, attrValue) + if firstAttrIndex == -1 { + firstAttrIndex = len(result) + result = append(result, nil) + } + } + + // If no attributes were found, just return the result now + if firstAttrIndex == -1 { + return g.Group(result) + } + + // Insert joined attribute at the position of the first attribute + result[firstAttrIndex] = g.Attr(name, strings.Join(attrValues, " ")) + return g.Group(result) +} + +type nodeTypeDescriber interface { + Type() g.NodeType +} + +func extractAttrValue(name string, n g.Node) (bool, string) { + // Ignore everything that is not an attribute + if n, ok := n.(nodeTypeDescriber); !ok || n.Type() == g.ElementType { + return false, "" + } + + var b strings.Builder + if err := n.Render(&b); err != nil { + return false, "" + } + + rendered := b.String() + if !strings.HasPrefix(rendered, " "+name+`="`) || !strings.HasSuffix(rendered, `"`) { + return false, "" + } + + v := strings.TrimPrefix(rendered, " "+name+`="`) + v = strings.TrimSuffix(v, `"`) + return true, v +}