feat(html): add standard boolean attribute helpers
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
9741659442
commit
dc00e10ec0
4 changed files with 142 additions and 2 deletions
|
|
@ -62,6 +62,11 @@ Accessibility-oriented helpers are also provided for common attribute patterns:
|
|||
- `AriaInvalid(node, invalid)`
|
||||
- `AriaRequired(node, required)`
|
||||
- `AriaReadOnly(node, readonly)`
|
||||
- `Disabled(node, disabled)`
|
||||
- `Checked(node, checked)`
|
||||
- `Required(node, required)`
|
||||
- `ReadOnly(node, readonly)`
|
||||
- `Selected(node, selected)`
|
||||
- `TabIndex(node, index)`
|
||||
- `AutoFocus(node)`
|
||||
- `ID(node, id)`
|
||||
|
|
|
|||
|
|
@ -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`, `AriaControls`, `AriaHasPopup`, `AriaOwns`, `AriaKeyShortcuts`, `Alt`/`AltText`, `AriaBusy`, `AriaLive`, `AriaAtomic`, `AriaDescription`, `AriaDetails`, `AriaErrorMessage`, `AriaRoleDescription`, `AriaDisabled`, `AriaModal`, `AriaChecked`, `AriaInvalid`, `AriaRequired`, `AriaPressed`, `AriaSelected`, `Hidden`, `TabIndex`, and `AutoFocus` helpers |
|
||||
| `node.go` | `Node` interface and all node types: `El`, `Text`, `Raw`, `If`, `Unless`, `Each`, `EachSeq`, `Switch`, `Entitled`, plus `AriaLabel`, `AriaControls`, `AriaHasPopup`, `AriaOwns`, `AriaKeyShortcuts`, `Alt`/`AltText`, `AriaBusy`, `AriaLive`, `AriaAtomic`, `AriaDescription`, `AriaDetails`, `AriaErrorMessage`, `AriaRoleDescription`, `AriaDisabled`, `AriaModal`, `AriaChecked`, `AriaInvalid`, `AriaRequired`, `AriaPressed`, `AriaSelected`, `Hidden`, `Disabled`, `Checked`, `Required`, `ReadOnly`, `Selected`, `TabIndex`, and `AutoFocus` helpers |
|
||||
| `layout.go` | HLCRF compositor with semantic HTML elements and ARIA roles |
|
||||
| `responsive.go` | Multi-variant breakpoint wrapper (`data-variant` containers, CSS scoping helpers) |
|
||||
| `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, and control-flow constructors (`If`, `Unless`, `Each`, `Switch`, `Entitled`), plus accessibility and visibility helpers such as `AriaLabel()`, `AriaControls()`, `AriaHasPopup()`, `AriaOwns()`, `AriaKeyShortcuts()`, `AriaCurrent()`, `AriaBusy()`, `AriaLive()`, `AriaAtomic()`, `AriaDescription()`, `AriaDetails()`, `AriaErrorMessage()`, `AriaRoleDescription()`, `AriaHidden()`, `Hidden()`, `AriaDisabled()`, `AriaModal()`, `AriaChecked()`, `AriaInvalid()`, `AriaRequired()`, `AriaReadOnly()`, and `TabIndex()`.
|
||||
**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, and control-flow constructors (`If`, `Unless`, `Each`, `Switch`, `Entitled`), plus accessibility and visibility helpers such as `AriaLabel()`, `AriaControls()`, `AriaHasPopup()`, `AriaOwns()`, `AriaKeyShortcuts()`, `AriaCurrent()`, `AriaBusy()`, `AriaLive()`, `AriaAtomic()`, `AriaDescription()`, `AriaDetails()`, `AriaErrorMessage()`, `AriaRoleDescription()`, `AriaHidden()`, `Hidden()`, `AriaDisabled()`, `AriaModal()`, `AriaChecked()`, `AriaInvalid()`, `AriaRequired()`, `AriaReadOnly()`, `Disabled()`, `Checked()`, `Required()`, `ReadOnly()`, `Selected()`, and `TabIndex()`.
|
||||
|
||||
**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`.
|
||||
|
||||
|
|
|
|||
55
node.go
55
node.go
|
|
@ -411,6 +411,61 @@ func Hidden(n Node, hidden bool) Node {
|
|||
return n
|
||||
}
|
||||
|
||||
// node.go: Disabled sets the HTML disabled attribute on an element node.
|
||||
// Example: Disabled(El("button"), true).
|
||||
// Disabled follows standard HTML boolean attribute semantics and omits the
|
||||
// attribute when false.
|
||||
func Disabled(n Node, disabled bool) Node {
|
||||
if disabled {
|
||||
return Attr(n, "disabled", "disabled")
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// node.go: Checked sets the HTML checked attribute on an element node.
|
||||
// Example: Checked(El("input"), true).
|
||||
// Checked follows standard HTML boolean attribute semantics and omits the
|
||||
// attribute when false.
|
||||
func Checked(n Node, checked bool) Node {
|
||||
if checked {
|
||||
return Attr(n, "checked", "checked")
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// node.go: Required sets the HTML required attribute on an element node.
|
||||
// Example: Required(El("input"), true).
|
||||
// Required follows standard HTML boolean attribute semantics and omits the
|
||||
// attribute when false.
|
||||
func Required(n Node, required bool) Node {
|
||||
if required {
|
||||
return Attr(n, "required", "required")
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// node.go: ReadOnly sets the HTML readonly attribute on an element node.
|
||||
// Example: ReadOnly(El("input"), true).
|
||||
// ReadOnly follows standard HTML boolean attribute semantics and omits the
|
||||
// attribute when false.
|
||||
func ReadOnly(n Node, readonly bool) Node {
|
||||
if readonly {
|
||||
return Attr(n, "readonly", "readonly")
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// node.go: Selected sets the HTML selected attribute on an element node.
|
||||
// Example: Selected(El("option"), true).
|
||||
// Selected follows standard HTML boolean attribute semantics and omits the
|
||||
// attribute when false.
|
||||
func Selected(n Node, selected bool) Node {
|
||||
if selected {
|
||||
return Attr(n, "selected", "selected")
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// node.go: AriaExpanded sets the aria-expanded attribute on an element node.
|
||||
// Example: AriaExpanded(El("button"), true).
|
||||
func AriaExpanded(n Node, expanded bool) Node {
|
||||
|
|
|
|||
80
node_test.go
80
node_test.go
|
|
@ -754,6 +754,86 @@ func TestHiddenHelper(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDisabledHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
|
||||
disabled := Disabled(El("button", Raw("menu")), true)
|
||||
gotDisabled := disabled.Render(ctx)
|
||||
if !strings.Contains(gotDisabled, `disabled="disabled"`) {
|
||||
t.Errorf("Disabled(true) = %q, want disabled=\"disabled\"", gotDisabled)
|
||||
}
|
||||
|
||||
enabled := Disabled(El("button", Raw("menu")), false)
|
||||
gotEnabled := enabled.Render(ctx)
|
||||
if strings.Contains(gotEnabled, `disabled=`) {
|
||||
t.Errorf("Disabled(false) = %q, want no disabled attribute", gotEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckedHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
|
||||
checked := Checked(El("input"), true)
|
||||
gotChecked := checked.Render(ctx)
|
||||
if !strings.Contains(gotChecked, `checked="checked"`) {
|
||||
t.Errorf("Checked(true) = %q, want checked=\"checked\"", gotChecked)
|
||||
}
|
||||
|
||||
unchecked := Checked(El("input"), false)
|
||||
gotUnchecked := unchecked.Render(ctx)
|
||||
if strings.Contains(gotUnchecked, `checked=`) {
|
||||
t.Errorf("Checked(false) = %q, want no checked attribute", gotUnchecked)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
|
||||
required := Required(El("input"), true)
|
||||
gotRequired := required.Render(ctx)
|
||||
if !strings.Contains(gotRequired, `required="required"`) {
|
||||
t.Errorf("Required(true) = %q, want required=\"required\"", gotRequired)
|
||||
}
|
||||
|
||||
optional := Required(El("input"), false)
|
||||
gotOptional := optional.Render(ctx)
|
||||
if strings.Contains(gotOptional, `required=`) {
|
||||
t.Errorf("Required(false) = %q, want no required attribute", gotOptional)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadOnlyHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
|
||||
readonly := ReadOnly(El("input"), true)
|
||||
gotReadonly := readonly.Render(ctx)
|
||||
if !strings.Contains(gotReadonly, `readonly="readonly"`) {
|
||||
t.Errorf("ReadOnly(true) = %q, want readonly=\"readonly\"", gotReadonly)
|
||||
}
|
||||
|
||||
editable := ReadOnly(El("input"), false)
|
||||
gotEditable := editable.Render(ctx)
|
||||
if strings.Contains(gotEditable, `readonly=`) {
|
||||
t.Errorf("ReadOnly(false) = %q, want no readonly attribute", gotEditable)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectedHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
|
||||
selected := Selected(El("option", Raw("menu")), true)
|
||||
gotSelected := selected.Render(ctx)
|
||||
if !strings.Contains(gotSelected, `selected="selected"`) {
|
||||
t.Errorf("Selected(true) = %q, want selected=\"selected\"", gotSelected)
|
||||
}
|
||||
|
||||
unselected := Selected(El("option", Raw("menu")), false)
|
||||
gotUnselected := unselected.Render(ctx)
|
||||
if strings.Contains(gotUnselected, `selected=`) {
|
||||
t.Errorf("Selected(false) = %q, want no selected attribute", gotUnselected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAriaExpandedHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue