From 149d31b140d024667697737720701c563cf27ec8 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 16:31:22 +0000 Subject: [PATCH] feat(html): add tabindex helper Co-Authored-By: Virgil --- docs/architecture.md | 7 +++++++ docs/history.md | 2 +- node.go | 6 ++++++ node_test.go | 9 +++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/architecture.md b/docs/architecture.md index a78bad6..186b5d9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -32,6 +32,13 @@ All concrete node types are unexported structs with exported constructor functio | `Switch(selector, cases)` | Renders one of several named cases based on a runtime selector function. Returns empty string when no case matches. | | `Entitled(feature, Node)` | Renders the child only when the context's entitlement function grants the named feature. Deny-by-default: returns empty string when no entitlement function is set. | +Accessibility-oriented helpers are also provided for common attribute patterns: + +- `AriaLabel(node, label)` +- `Alt(node, text)` +- `AriaHidden(node, hidden)` +- `TabIndex(node, index)` + ### Safety Guarantees - **XSS prevention**: `Text()` nodes always HTML-escape their output via `html.EscapeString()`. User-supplied strings passed through `Text()` cannot inject HTML. diff --git a/docs/history.md b/docs/history.md index 4631ef2..6816f38 100644 --- a/docs/history.md +++ b/docs/history.md @@ -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`, `alt`, `aria-hidden`, and `tabindex` helpers. The layout has semantic HTML and ARIA roles, and the node layer now exposes common accessibility attribute shortcuts 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. diff --git a/node.go b/node.go index 24a2da3..be50b70 100644 --- a/node.go +++ b/node.go @@ -5,6 +5,7 @@ import ( "iter" "maps" "slices" + "strconv" "strings" i18n "dappco.re/go/core/i18n" @@ -115,6 +116,11 @@ func AriaHidden(n Node, hidden bool) Node { return Attr(n, "aria-hidden", "false") } +// TabIndex sets the tabindex attribute on an element node. +func TabIndex(n Node, index int) Node { + return Attr(n, "tabindex", strconv.Itoa(index)) +} + func (n *elNode) Render(ctx *Context) string { var b strings.Builder diff --git a/node_test.go b/node_test.go index 4764bac..f55b5d7 100644 --- a/node_test.go +++ b/node_test.go @@ -211,6 +211,15 @@ func TestAriaHiddenHelper(t *testing.T) { } } +func TestTabIndexHelper(t *testing.T) { + ctx := NewContext() + node := TabIndex(El("button", Raw("action")), -1) + got := node.Render(ctx) + if !strings.Contains(got, `tabindex="-1"`) { + t.Errorf("TabIndex() = %q, want tabindex=\"-1\"", got) + } +} + func TestElNode_MultipleAttrs(t *testing.T) { ctx := NewContext() node := Attr(Attr(El("a", Raw("link")), "href", "/home"), "class", "nav")