Add layout components 0001-0025

Stack through StickyHeader, with behavior tests and gallery demos.
kit gains Arrive, Settle (paint-time layout check) and Shade.
Several actions return named ui.Element results; mygo vet forbids
elements in structs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-17 02:49:32 -04:00
1 parent b34562333b
commit 4efaf2680f
41 files changed
+1765 -11

No files matched your search

+11 -2
View File
@@ -116,8 +116,13 @@ func TestRulesCatch(t *testing.T) {
{"directive", "", "package x\n// Files.\n//go:generate echo\n", comments, 0},
{"good", "button/icon_button.go", head + "func IconButton(c *ui.Context, o IconButtonOptions) ui.Element\n" +
"type IconButtonOptions struct{}\n", components, 0},
{"result", "button/split.go", head + "func Split(c *ui.Context) SplitResult\ntype SplitResult struct{}\n",
components, 0},
{"parts", "button/split.go", head + "func Split(c *ui.Context) (root, menu ui.Element)\n", components, 0},
{"struct result", "button/split.go", head + "func Split(c *ui.Context) SplitResult\ntype SplitResult struct{}\n",
components, 1},
{"unnamed parts", "button/split.go", head + "func Split(c *ui.Context) (ui.Element, ui.Element)\n",
components, 1},
{"root not first", "button/split.go", head + "func Split(c *ui.Context) (menu, root ui.Element)\n",
components, 1},
{"two", "button/a.go", head + "func A(c *ui.Context) ui.Element\nfunc B(c *ui.Context) ui.Element\n",
components, 1},
{"file name", "button/b.go", head + "func KPIGrid(c *ui.Context) ui.Element\n", components, 1},
@@ -127,6 +132,10 @@ func TestRulesCatch(t *testing.T) {
components, 1},
{"returns", "button/a.go", head + "func A(c *ui.Context) int\n", components, 1},
{"ctx name", "button/a.go", head + "func A(ctx *ui.Context) ui.Element\n", components, 1},
{"content after options", "button/a.go", head + "func A(c *ui.Context, o AOptions, content func()) ui.Element\n",
components, 0},
{"two after options", "button/a.go", head + "func A(c *ui.Context, o AOptions, a, b func()) ui.Element\n",
components, 1},
{"grouped options", "button/a.go", head + "func A(c *ui.Context, x, y AOptions) ui.Element\n",
components, 1},
}
+17 -5
View File
@@ -107,21 +107,29 @@ func signature(fn *ast.FuncDecl) string {
ps[0].Names[0].Name != "c" {
return "must take c *ui.Context first"
}
last := ps[len(ps)-1]
contentLast := typeString(last.Type) == "func()" && len(last.Names) == 1
for i, p := range ps {
ts := typeString(p.Type)
if _, ok := p.Type.(*ast.Ellipsis); ok {
return "must not be variadic"
}
if strings.HasSuffix(ts, "Options") && (ts != name+"Options" || i != len(ps)-1 || len(p.Names) > 1) {
if strings.HasSuffix(ts, "Options") && (ts != name+"Options" || i != len(ps)-1 && !(i == len(ps)-2 && contentLast) ||
len(p.Names) > 1) {
return "options must be a final " + name + "Options"
}
}
rs := fn.Type.Results
if rs == nil || rs.NumFields() != 1 {
return "must return one value"
if rs == nil || rs.NumFields() == 0 {
return "must return ui.Element"
}
if r := typeString(rs.List[0].Type); r != "ui.Element" && r != name+"Result" {
return "must return ui.Element or " + name + "Result"
for _, r := range rs.List {
if typeString(r.Type) != "ui.Element" {
return "must return only ui.Element results"
}
}
if rs.NumFields() > 1 && (len(rs.List[0].Names) == 0 || rs.List[0].Names[0].Name != "root") {
return "several results must be named, root first"
}
return ""
}
@@ -134,6 +142,10 @@ func typeString(e ast.Expr) string {
return "*" + typeString(t.X)
case *ast.SelectorExpr:
return typeString(t.X) + "." + t.Sel.Name
case *ast.FuncType:
if t.Params.NumFields() == 0 && t.Results.NumFields() == 0 {
return "func()"
}
}
return fmt.Sprintf("%T", e)
}