diff --git a/context.go b/context.go index 3fb7828..b8492f9 100644 --- a/context.go +++ b/context.go @@ -1,11 +1,13 @@ package html -// Translator provides Text() lookups for a rendering context. +// context.go: Translator provides Text() lookups for a rendering context. +// Example: a locale-aware service can satisfy T(key, args...). type Translator interface { T(key string, args ...any) string } -// Context carries rendering state through the node tree. +// context.go: Context carries rendering state through the node tree. +// Example: NewContext("en-GB") initialises locale-specific rendering state. type Context struct { Identity string Locale string @@ -31,7 +33,8 @@ func applyLocaleToService(svc Translator, locale string) { } } -// NewContext creates a new rendering context with sensible defaults. +// context.go: NewContext creates a new rendering context with sensible defaults. +// Example: ctx := NewContext("en-GB"). // An optional locale may be provided as the first argument. func NewContext(locale ...string) *Context { ctx := &Context{ @@ -43,7 +46,8 @@ func NewContext(locale ...string) *Context { return ctx } -// NewContextWithService creates a rendering context backed by a specific translator. +// context.go: NewContextWithService creates a rendering context backed by a specific translator. +// Example: ctx := NewContextWithService(svc, "fr-FR"). // An optional locale may be provided as the second argument. func NewContextWithService(svc Translator, locale ...string) *Context { ctx := NewContext(locale...) @@ -51,7 +55,8 @@ func NewContextWithService(svc Translator, locale ...string) *Context { return ctx } -// SetService swaps the translator used by the context. +// context.go: SetService swaps the translator used by the context. +// Example: ctx.SetService(svc). func (ctx *Context) SetService(svc Translator) *Context { if ctx == nil { return nil @@ -62,7 +67,8 @@ func (ctx *Context) SetService(svc Translator) *Context { return ctx } -// SetLocale updates the context locale and reapplies it to the active translator. +// context.go: SetLocale updates the context locale and reapplies it to the active translator. +// Example: ctx.SetLocale("en-US"). func (ctx *Context) SetLocale(locale string) *Context { if ctx == nil { return nil diff --git a/layout.go b/layout.go index 165c842..94c05a5 100644 --- a/layout.go +++ b/layout.go @@ -27,8 +27,9 @@ var slotRegistry = map[byte]slotMeta{ 'F': {tag: "footer", role: "contentinfo"}, } -// Layout is an HLCRF compositor. Arranges nodes into semantic HTML regions +// layout.go: Layout is an HLCRF compositor. Arranges nodes into semantic HTML regions // with deterministic path-based IDs. +// Example: NewLayout("HCF").H(Raw("head")).C(Raw("body")).F(Raw("foot")). type Layout struct { variant string // "HLCRF", "HCF", "C", etc. path string // "" for root, "L-0-" for nested @@ -36,7 +37,8 @@ type Layout struct { variantErr error } -// NewLayout creates a new Layout with the given variant string. +// layout.go: NewLayout creates a new Layout with the given variant string. +// Example: page := NewLayout("HCF"). // The variant determines which slots are rendered (e.g., "HLCRF", "HCF", "C"). func NewLayout(variant string) *Layout { l := &Layout{ @@ -47,8 +49,9 @@ func NewLayout(variant string) *Layout { return l } -// ValidateLayoutVariant reports whether a layout variant string contains only +// layout.go: ValidateLayoutVariant reports whether a layout variant string contains only // recognised slot characters. +// Example: ValidateLayoutVariant("HCF"). func ValidateLayoutVariant(variant string) error { var invalid bool for i := range len(variant) { @@ -99,8 +102,9 @@ func (l *Layout) blockID(slot byte) string { return l.path + string(slot) + "-0" } -// VariantError reports whether the layout variant string contained any invalid +// layout.go: VariantError reports whether the layout variant string contained any invalid // slot characters when the layout was constructed. +// Example: NewLayout("HXC").VariantError(). func (l *Layout) VariantError() error { if l == nil { return nil @@ -108,7 +112,8 @@ func (l *Layout) VariantError() error { return l.variantErr } -// Render produces the semantic HTML for this layout. +// layout.go: Render produces the semantic HTML for this layout. +// Example: NewLayout("C").C(Raw("body")).Render(NewContext()). // Only slots present in the variant string are rendered. func (l *Layout) Render(ctx *Context) string { if l == nil { diff --git a/node.go b/node.go index ac44af5..de832e1 100644 --- a/node.go +++ b/node.go @@ -11,7 +11,8 @@ import ( i18n "dappco.re/go/core/i18n" ) -// Node is anything renderable. +// node.go: Node is anything renderable. +// Example: El("p", Text("page.body")) returns a Node that can be passed to Render(). type Node interface { Render(ctx *Context) string } @@ -119,7 +120,8 @@ type rawNode struct { content string } -// Raw creates a node that renders without escaping (escape hatch for trusted content). +// node.go: Raw creates a node that renders without escaping. +// Example: Raw("trusted") preserves the HTML verbatim. func Raw(content string) Node { return &rawNode{content: content} } @@ -139,7 +141,8 @@ type elNode struct { attrs map[string]string } -// El creates an HTML element node with children. +// node.go: El creates an HTML element node with children. +// Example: El("nav", Text("nav.label")) renders a semantic element with nested nodes. func El(tag string, children ...Node) Node { return &elNode{ tag: tag, @@ -148,9 +151,11 @@ func El(tag string, children ...Node) Node { } } -// Attr sets an attribute on an El node. Returns the node for chaining. -// It recursively traverses through wrappers like If, Unless, Entitled, -// Switch, and Each/EachSeq so the attribute lands on the rendered element. +// node.go: Attr sets an attribute on an El node and returns the same node for chaining. +// Example: Attr(El("img"), "alt", "Logo") adds an escaped alt attribute. +// +// It recursively traverses wrappers like If, Unless, Entitled, Switch, +// and Each/EachSeq so the attribute lands on the rendered element. func Attr(n Node, key, value string) Node { switch t := n.(type) { case *elNode: @@ -171,34 +176,40 @@ func Attr(n Node, key, value string) Node { return n } -// AriaLabel sets the aria-label attribute on an element node. +// node.go: AriaLabel sets the aria-label attribute on an element node. +// Example: AriaLabel(El("button"), "Open menu"). func AriaLabel(n Node, label string) Node { return Attr(n, "aria-label", label) } -// AriaDescribedBy sets the aria-describedby attribute on an element node. +// node.go: AriaDescribedBy sets the aria-describedby attribute on an element node. +// Example: AriaDescribedBy(El("input"), "help-text", "error-text"). // Multiple IDs are joined with spaces, matching the HTML attribute format. func AriaDescribedBy(n Node, ids ...string) Node { return Attr(n, "aria-describedby", strings.Join(ids, " ")) } -// AriaLabelledBy sets the aria-labelledby attribute on an element node. +// node.go: AriaLabelledBy sets the aria-labelledby attribute on an element node. +// Example: AriaLabelledBy(El("section"), "section-title"). // Multiple IDs are joined with spaces, matching the HTML attribute format. func AriaLabelledBy(n Node, ids ...string) Node { return Attr(n, "aria-labelledby", strings.Join(ids, " ")) } -// Role sets the role attribute on an element node. +// node.go: Role sets the role attribute on an element node. +// Example: Role(El("aside"), "complementary"). func Role(n Node, role string) Node { return Attr(n, "role", role) } -// Alt sets the alt attribute on an element node. +// node.go: Alt sets the alt attribute on an element node. +// Example: Alt(El("img"), "Product screenshot"). func Alt(n Node, text string) Node { return Attr(n, "alt", text) } -// AriaHidden sets the aria-hidden attribute on an element node. +// node.go: AriaHidden sets the aria-hidden attribute on an element node. +// Example: AriaHidden(El("svg"), true). func AriaHidden(n Node, hidden bool) Node { if hidden { return Attr(n, "aria-hidden", "true") @@ -206,12 +217,14 @@ func AriaHidden(n Node, hidden bool) Node { return Attr(n, "aria-hidden", "false") } -// TabIndex sets the tabindex attribute on an element node. +// node.go: TabIndex sets the tabindex attribute on an element node. +// Example: TabIndex(El("button"), 0). func TabIndex(n Node, index int) Node { return Attr(n, "tabindex", strconv.Itoa(index)) } -// AutoFocus sets the autofocus attribute on an element node. +// node.go: AutoFocus sets the autofocus attribute on an element node. +// Example: AutoFocus(El("input")). func AutoFocus(n Node) Node { return Attr(n, "autofocus", "autofocus") } @@ -268,7 +281,8 @@ type textNode struct { args []any } -// Text creates a node that renders through the go-i18n grammar pipeline. +// node.go: Text creates a node that renders through the go-i18n grammar pipeline. +// Example: Text("page.title") renders translated text and escapes it for HTML. // Output is HTML-escaped by default. Safe-by-default path. func Text(key string, args ...any) Node { return &textNode{key: key, args: args} @@ -295,7 +309,8 @@ type ifNode struct { node Node } -// If renders child only when condition is true. +// node.go: If renders child only when condition is true. +// Example: If(func(*Context) bool { return true }, Raw("shown")). func If(cond func(*Context) bool, node Node) Node { return &ifNode{cond: cond, node: node} } @@ -318,7 +333,8 @@ type unlessNode struct { node Node } -// Unless renders child only when condition is false. +// node.go: Unless renders child only when condition is false. +// Example: Unless(func(*Context) bool { return true }, Raw("hidden")). func Unless(cond func(*Context) bool, node Node) Node { return &unlessNode{cond: cond, node: node} } @@ -341,8 +357,10 @@ type entitledNode struct { node Node } -// Entitled renders child only when entitlement is granted. Absent, not hidden. -// If no entitlement function is set on the context, access is denied by default. +// node.go: Entitled renders child only when entitlement is granted. +// Example: Entitled("premium", Raw("paid feature")). +// Content is absent, not hidden. If no entitlement function is set on the +// context, access is denied by default. func Entitled(feature string, node Node) Node { return &entitledNode{feature: feature, node: node} } @@ -365,7 +383,8 @@ type switchNode struct { cases map[string]Node } -// Switch renders based on runtime selector value. +// node.go: Switch renders based on runtime selector value. +// Example: Switch(selector, map[string]Node{"desktop": Raw("wide")}). func Switch(selector func(*Context) string, cases map[string]Node) Node { return &switchNode{selector: selector, cases: cases} } @@ -389,12 +408,14 @@ type eachNode[T any] struct { fn func(T) Node } -// Each iterates items and renders each via fn. +// node.go: Each iterates items and renders each via fn. +// Example: Each(items, func(item Item) Node { return El("li", Text(item.Name)) }). func Each[T any](items []T, fn func(T) Node) Node { return EachSeq(slices.Values(items), fn) } -// EachSeq iterates an iter.Seq and renders each via fn. +// node.go: EachSeq iterates an iter.Seq and renders each via fn. +// Example: EachSeq(slices.Values(items), renderItem). func EachSeq[T any](items iter.Seq[T], fn func(T) Node) Node { return &eachNode[T]{items: items, fn: fn} } diff --git a/path.go b/path.go index bcb2e1f..0f3ab9b 100644 --- a/path.go +++ b/path.go @@ -2,7 +2,7 @@ package html import "strings" -// ParseBlockID extracts the slot sequence from a data-block ID. +// path.go: ParseBlockID extracts the slot sequence from a data-block ID. // Example: ParseBlockID("L-0-C-0") returns []byte{'L', 'C'}. func ParseBlockID(id string) []byte { if id == "" { diff --git a/pipeline.go b/pipeline.go index 729614e..a675c03 100644 --- a/pipeline.go +++ b/pipeline.go @@ -8,7 +8,8 @@ import ( "dappco.re/go/core/i18n/reversal" ) -// StripTags removes HTML tags from rendered output, returning plain text. +// pipeline.go: StripTags removes HTML tags from rendered output, returning plain text. +// Example: StripTags("

Hello

world

") returns "Hello world". // Tag boundaries are collapsed into single spaces; result is trimmed. // Does not handle script/style element content (go-html does not generate these). func StripTags(html string) string { @@ -43,8 +44,9 @@ func StripTags(html string) string { return strings.TrimSpace(b.String()) } -// Imprint renders a node tree to HTML, strips tags, tokenises the text, +// pipeline.go: Imprint renders a node tree to HTML, strips tags, tokenises the text, // and returns a GrammarImprint — the full render-reverse pipeline. +// Example: Imprint(NewLayout("C").C(Text("page.body")), NewContext()). func Imprint(node Node, ctx *Context) reversal.GrammarImprint { if ctx == nil { ctx = NewContext() @@ -56,8 +58,9 @@ func Imprint(node Node, ctx *Context) reversal.GrammarImprint { return reversal.NewImprint(tokens) } -// CompareVariants runs the imprint pipeline on each responsive variant independently +// pipeline.go: CompareVariants runs the imprint pipeline on each responsive variant independently // and returns pairwise similarity scores. Key format: "name1:name2". +// Example: CompareVariants(NewResponsive().Variant("desktop", NewLayout("C")), NewContext()). func CompareVariants(r *Responsive, ctx *Context) map[string]float64 { if ctx == nil { ctx = NewContext() diff --git a/render.go b/render.go index 93a2529..e7c90a3 100644 --- a/render.go +++ b/render.go @@ -1,6 +1,7 @@ package html -// Render is a convenience function that renders a node tree to HTML. +// render.go: Render is a convenience function that renders a node tree to HTML. +// Example: Render(NewLayout("C").C(Raw("body")), NewContext()). func Render(node Node, ctx *Context) string { if ctx == nil { ctx = NewContext() diff --git a/responsive.go b/responsive.go index 61b8131..59aa642 100644 --- a/responsive.go +++ b/responsive.go @@ -2,7 +2,8 @@ package html import "strings" -// Responsive wraps multiple Layout variants for breakpoint-aware rendering. +// responsive.go: Responsive wraps multiple Layout variants for breakpoint-aware rendering. +// Example: NewResponsive().Variant("desktop", NewLayout("C").C(Raw("main"))). // Each variant is rendered inside a container with data-variant for CSS targeting. type Responsive struct { variants []responsiveVariant @@ -13,7 +14,8 @@ type responsiveVariant struct { layout *Layout } -// NewResponsive creates a new multi-variant responsive compositor. +// responsive.go: NewResponsive creates a new multi-variant responsive compositor. +// Example: r := NewResponsive(). func NewResponsive() *Responsive { return &Responsive{} } @@ -40,14 +42,15 @@ func escapeCSSString(s string) string { return b.String() } -// VariantSelector returns a CSS attribute selector for a named responsive -// variant. +// responsive.go: VariantSelector returns a CSS attribute selector for a named responsive variant. +// Example: VariantSelector("desktop") returns [data-variant="desktop"]. func VariantSelector(name string) string { return `[data-variant="` + escapeCSSString(name) + `"]` } -// ScopeVariant prefixes a selector so it only matches elements inside the +// responsive.go: ScopeVariant prefixes a selector so it only matches elements inside the // named responsive variant. +// Example: ScopeVariant("desktop", ".nav"). func ScopeVariant(name, selector string) string { scope := VariantSelector(name) if selector == "" { @@ -56,14 +59,16 @@ func ScopeVariant(name, selector string) string { return scope + " " + selector } -// Variant adds a named layout variant (e.g., "desktop", "tablet", "mobile"). +// responsive.go: Variant adds a named layout variant (e.g., "desktop", "tablet", "mobile"). +// Example: r.Variant("mobile", NewLayout("C").C(Raw("body"))). // Variants render in insertion order. func (r *Responsive) Variant(name string, layout *Layout) *Responsive { r.variants = append(r.variants, responsiveVariant{name: name, layout: layout}) return r } -// Render produces HTML with each variant in a data-variant container. +// responsive.go: Render produces HTML with each variant in a data-variant container. +// Example: NewResponsive().Variant("desktop", NewLayout("C")).Render(NewContext()). func (r *Responsive) Render(ctx *Context) string { if r == nil { return ""