diff --git a/go.work b/go.work new file mode 100644 index 0000000..957f5b9 --- /dev/null +++ b/go.work @@ -0,0 +1,3 @@ +go 1.26.0 + +use . diff --git a/layout.go b/layout.go index fa600ac..b75c5ed 100644 --- a/layout.go +++ b/layout.go @@ -5,7 +5,10 @@ package html // dappco.re/go/core here — it transitively pulls in fmt/os/log (~500 KB+). // The stdlib errors package is safe for WASM. -import "errors" +import ( + "errors" + "strconv" +) // Compile-time interface check. var _ Node = (*Layout)(nil) @@ -49,51 +52,7 @@ func renderWithLayoutPath(node Node, ctx *Context, path string) string { return renderer.renderWithLayoutPath(ctx, path) } - switch t := node.(type) { - case *Layout: - if t == nil { - return "" - } - clone := *t - clone.path = path - return clone.Render(ctx) - case *ifNode: - if t == nil || t.cond == nil || t.node == nil { - return "" - } - if t.cond(ctx) { - return renderWithLayoutPath(t.node, ctx, path) - } - return "" - case *unlessNode: - if t == nil || t.cond == nil || t.node == nil { - return "" - } - if !t.cond(ctx) { - return renderWithLayoutPath(t.node, ctx, path) - } - return "" - case *entitledNode: - if t == nil || t.node == nil { - return "" - } - if ctx == nil || ctx.Entitlements == nil || !ctx.Entitlements(t.feature) { - return "" - } - return renderWithLayoutPath(t.node, ctx, path) - case *switchNode: - if t == nil || t.selector == nil || t.cases == nil { - return "" - } - key := t.selector(ctx) - node, ok := t.cases[key] - if !ok || node == nil { - return "" - } - return renderWithLayoutPath(node, ctx, path) - default: - return node.Render(ctx) - } + return node.Render(ctx) } // NewLayout creates a new Layout with the given variant string. @@ -237,11 +196,11 @@ func (l *Layout) Render(ctx *Context) string { b.WriteString(escapeAttr(bid)) b.WriteString(`">`) - for _, child := range children { + for i, child := range children { if child == nil { continue } - b.WriteString(renderWithLayoutPath(child, ctx, bid+"-")) + b.WriteString(renderWithLayoutPath(child, ctx, bid+"."+strconv.Itoa(i))) } b.WriteString(" 1 { + last := tokens[len(tokens)-1] + if len(last) == 1 { + if _, ok := slotRegistry[last[0]]; ok { + return nil + } + } + } + + for i, token := range tokens { + if len(token) == 1 { + if _, ok := slotRegistry[token[0]]; ok { + slots = append(slots, token[0]) + continue + } + } + + if !allDigits(token) { + return nil + } + if i == 0 { + return nil + } + switch seps[i-1] { + case '-': + if token != "0" { + return nil + } + case '.': + default: + return nil + } + } + + if len(slots) == 0 { + return nil } return slots } + +// trimBlockPath removes the trailing child coordinate from a block path when +// the final segment is numeric. It keeps slot roots like "C-0" intact while +// trimming nested coordinates such as "C-0.1" or "C-0.1.2" back to the parent +// path. +func trimBlockPath(path string) string { + if path == "" { + return "" + } + + lastDot := strings.LastIndexByte(path, '.') + if lastDot < 0 || lastDot == len(path)-1 { + return path + } + + for i := lastDot + 1; i < len(path); i++ { + ch := path[i] + if ch < '0' || ch > '9' { + return path + } + } + + return path[:lastDot] +} + +func allDigits(s string) bool { + if s == "" { + return false + } + for i := 0; i < len(s); i++ { + ch := s[i] + if ch < '0' || ch > '9' { + return false + } + } + return true +} diff --git a/path_test.go b/path_test.go index 51bb77c..4b6de30 100644 --- a/path_test.go +++ b/path_test.go @@ -66,6 +66,9 @@ func TestParseBlockID_ExtractsSlots_Good(t *testing.T) { }{ {"L-0-C-0", []byte{'L', 'C'}}, {"L.0.C.0", []byte{'L', 'C'}}, + {"C-0.0", []byte{'C'}}, + {"C.2.1", []byte{'C'}}, + {"C-0.1.2", []byte{'C'}}, {"H", []byte{'H'}}, {"H-0", []byte{'H'}}, {"C-0-C-0-C-0", []byte{'C', 'C', 'C'}},