docs: align html API comments with AX guidance
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
a86c8ef770
commit
c84bd21cf4
7 changed files with 86 additions and 45 deletions
18
context.go
18
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
|
||||
|
|
|
|||
15
layout.go
15
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 {
|
||||
|
|
|
|||
65
node.go
65
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("<strong>trusted</strong>") 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}
|
||||
}
|
||||
|
|
|
|||
2
path.go
2
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 == "" {
|
||||
|
|
|
|||
|
|
@ -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("<p>Hello</p><p>world</p>") 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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue