feat(html): add focus management helpers
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
4ae93ce36f
commit
c1852f86aa
5 changed files with 37 additions and 4 deletions
|
|
@ -17,7 +17,7 @@ type Node interface {
|
|||
}
|
||||
```
|
||||
|
||||
All concrete node types are unexported structs with exported constructor functions. The public API surface consists of nine node constructors, two accessibility helpers, plus the `Attr()` and `Render()` helpers:
|
||||
All concrete node types are unexported structs with exported constructor functions. The public API surface consists of nine node constructors, four accessibility helpers, plus the `Attr()` and `Render()` helpers:
|
||||
|
||||
| Constructor | Behaviour |
|
||||
|-------------|-----------|
|
||||
|
|
@ -25,6 +25,8 @@ All concrete node types are unexported structs with exported constructor functio
|
|||
| `Attr(Node, key, value)` | Sets an attribute on an `El` node. Traverses through `If`, `Unless`, and `Entitled` wrappers. Returns the node for chaining. |
|
||||
| `AriaLabel(Node, label)` | Convenience helper that sets `aria-label` on an element node. |
|
||||
| `AltText(Node, text)` | Convenience helper that sets `alt` on an element node. |
|
||||
| `TabIndex(Node, index)` | Convenience helper that sets `tabindex` on an element node. |
|
||||
| `AutoFocus(Node)` | Convenience helper that sets `autofocus` on an element node. |
|
||||
| `Text(key, ...any)` | Translated text via the active context translator. Server builds fall back to global `go-i18n`; JS builds fall back to the key. Output is always HTML-escaped. |
|
||||
| `Raw(content)` | Unescaped trusted content. Explicit escape hatch. |
|
||||
| `If(cond, Node)` | Renders the child only when the condition function returns true. |
|
||||
|
|
|
|||
|
|
@ -114,6 +114,6 @@ These are not regressions; they are design choices or deferred work recorded for
|
|||
These items were captured during the WASM size reduction work and expert review sessions. They are not committed work items.
|
||||
|
||||
- **TypeScript type definitions** alongside `GenerateBundle()` for typed Web Component consumers.
|
||||
- **Accessibility helpers** — `aria-label` builder, `alt` text helpers, focus management nodes. The layout has semantic HTML and ARIA roles but no API for fine-grained accessibility attributes beyond `Attr()`.
|
||||
- **Accessibility helpers** — `aria-label` builder, `alt` text helpers, and focus management helpers (`TabIndex`, `AutoFocus`). The layout has semantic HTML and ARIA roles but no API for fine-grained accessibility attributes beyond `Attr()`.
|
||||
- **Layout variant validation** — return a warning or sentinel error from `NewLayout` when the variant string contains unrecognised slot characters.
|
||||
- **Daemon mode for codegen** — watch mode for regenerating the JS bundle when slot config changes, for development workflows.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
|
|||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `node.go` | `Node` interface and all node types: `El`, `Text`, `Raw`, `If`, `Unless`, `Each`, `EachSeq`, `Switch`, `Entitled`, plus `AriaLabel` and `AltText` helpers |
|
||||
| `node.go` | `Node` interface and all node types: `El`, `Text`, `Raw`, `If`, `Unless`, `Each`, `EachSeq`, `Switch`, `Entitled`, plus `AriaLabel`, `AltText`, `TabIndex`, and `AutoFocus` helpers |
|
||||
| `layout.go` | HLCRF compositor with semantic HTML elements and ARIA roles |
|
||||
| `responsive.go` | Multi-variant breakpoint wrapper (`data-variant` containers) |
|
||||
| `context.go` | Rendering context: identity, locale, entitlements, i18n service |
|
||||
|
|
@ -52,7 +52,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
|
|||
|
||||
## Key Concepts
|
||||
|
||||
**Node tree** -- All renderable units implement `Node`, a single-method interface: `Render(ctx *Context) string`. The library composes nodes into trees using `El()` for elements, `Text()` for translated text, control-flow constructors (`If`, `Unless`, `Each`, `Switch`, `Entitled`), and accessibility helpers (`AriaLabel`, `AltText`).
|
||||
**Node tree** -- All renderable units implement `Node`, a single-method interface: `Render(ctx *Context) string`. The library composes nodes into trees using `El()` for elements, `Text()` for translated text, control-flow constructors (`If`, `Unless`, `Each`, `Switch`, `Entitled`), and accessibility helpers (`AriaLabel`, `AltText`, `TabIndex`, `AutoFocus`).
|
||||
|
||||
**HLCRF Layout** -- A five-slot compositor that maps to semantic HTML: `<header>` (H), `<aside>` (L/R), `<main>` (C), `<footer>` (F). The variant string controls which slots render: `"HLCRF"` for all five, `"HCF"` for three, `"C"` for content only. Layouts nest: placing a `Layout` inside another layout's slot produces hierarchical `data-block` paths like `L-0-C-0`.
|
||||
|
||||
|
|
|
|||
13
node.go
13
node.go
|
|
@ -5,6 +5,7 @@ import (
|
|||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Node is anything renderable.
|
||||
|
|
@ -117,6 +118,18 @@ func AltText(n Node, text string) Node {
|
|||
return Attr(n, "alt", text)
|
||||
}
|
||||
|
||||
// TabIndex sets a tabindex attribute on an element node.
|
||||
// Usage example: TabIndex(El("button", Text("save")), 0)
|
||||
func TabIndex(n Node, index int) Node {
|
||||
return Attr(n, "tabindex", strconv.Itoa(index))
|
||||
}
|
||||
|
||||
// AutoFocus sets an autofocus attribute on an element node.
|
||||
// Usage example: AutoFocus(El("input"))
|
||||
func AutoFocus(n Node) Node {
|
||||
return Attr(n, "autofocus", "autofocus")
|
||||
}
|
||||
|
||||
func (n *elNode) Render(ctx *Context) string {
|
||||
if n == nil {
|
||||
return ""
|
||||
|
|
|
|||
18
node_test.go
18
node_test.go
|
|
@ -193,6 +193,24 @@ func TestAltText_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTabIndex_Good(t *testing.T) {
|
||||
node := TabIndex(El("button", Raw("save")), 0)
|
||||
got := node.Render(NewContext())
|
||||
want := `<button tabindex="0">save</button>`
|
||||
if got != want {
|
||||
t.Errorf("TabIndex() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoFocus_Good(t *testing.T) {
|
||||
node := AutoFocus(El("input"))
|
||||
got := node.Render(NewContext())
|
||||
want := `<input autofocus="autofocus">`
|
||||
if got != want {
|
||||
t.Errorf("AutoFocus() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestElNode_MultipleAttrs_Good(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := Attr(Attr(El("a", Raw("link")), "href", "/home"), "class", "nav")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue