fix(html): align left slot with navigation semantics
This commit is contained in:
parent
09b48e00dc
commit
61adbd55c5
6 changed files with 22 additions and 7 deletions
|
|
@ -73,7 +73,7 @@ The `Layout` type is a compositor for five named slots:
|
|||
| Slot Letter | Semantic Element | ARIA Role | Accessor |
|
||||
|-------------|-----------------|-----------|----------|
|
||||
| H | `<header>` | `banner` | `layout.H(...)` |
|
||||
| L | `<aside>` | `complementary` | `layout.L(...)` |
|
||||
| L | `<nav>` | `navigation` | `layout.L(...)` |
|
||||
| C | `<main>` | `main` | `layout.C(...)` |
|
||||
| R | `<aside>` | `complementary` | `layout.R(...)` |
|
||||
| F | `<footer>` | `contentinfo` | `layout.F(...)` |
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
|
|||
|
||||
**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`, `Role`).
|
||||
|
||||
**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`.
|
||||
**HLCRF Layout** -- A five-slot compositor that maps to semantic HTML: `<header>` (H), `<nav>` (L), `<main>` (C), `<aside>` (R), `<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`.
|
||||
|
||||
**Responsive variants** -- `Responsive` wraps multiple `Layout` instances with named breakpoints (e.g. `"desktop"`, `"mobile"`). Each variant renders inside a `<div data-variant="name">` container for CSS or JavaScript targeting. `VariantSelector(name)` returns a ready-made attribute selector for styling these containers from CSS.
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ type slotMeta struct {
|
|||
// slotRegistry maps slot letters to their semantic HTML elements and ARIA roles.
|
||||
var slotRegistry = map[byte]slotMeta{
|
||||
'H': {tag: "header", role: "banner"},
|
||||
'L': {tag: "aside", role: "complementary"},
|
||||
'L': {tag: "nav", role: "navigation"},
|
||||
'C': {tag: "main", role: "main"},
|
||||
'R': {tag: "aside", role: "complementary"},
|
||||
'F': {tag: "footer", role: "contentinfo"},
|
||||
|
|
@ -148,7 +148,7 @@ func (l *Layout) H(nodes ...Node) *Layout {
|
|||
return l
|
||||
}
|
||||
|
||||
// L appends nodes to the Left aside slot.
|
||||
// L appends nodes to the Left navigation slot.
|
||||
// Usage example: NewLayout("LC").L(Text("nav"))
|
||||
func (l *Layout) L(nodes ...Node) *Layout {
|
||||
if l == nil {
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ func TestLayout_HLCRF_Good(t *testing.T) {
|
|||
got := layout.Render(ctx)
|
||||
|
||||
// Must contain semantic elements
|
||||
for _, want := range []string{"<header", "<aside", "<main", "<footer"} {
|
||||
for _, want := range []string{"<header", "<nav", "<main", "<footer"} {
|
||||
if !containsText(got, want) {
|
||||
t.Errorf("HLCRF layout missing %q in:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Must contain ARIA roles
|
||||
for _, want := range []string{`role="banner"`, `role="complementary"`, `role="main"`, `role="contentinfo"`} {
|
||||
for _, want := range []string{`role="banner"`, `role="navigation"`, `role="main"`, `role="contentinfo"`} {
|
||||
if !containsText(got, want) {
|
||||
t.Errorf("HLCRF layout missing role %q in:\n%s", want, got)
|
||||
}
|
||||
|
|
|
|||
15
path.go
15
path.go
|
|
@ -15,9 +15,22 @@ func ParseBlockID(id string) []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Accept both the current "{slot}-0" path format and the dot notation
|
||||
// used in the RFC prose examples. A plain single-slot ID such as "H" is
|
||||
// also valid.
|
||||
normalized := strings.ReplaceAll(id, ".", "-")
|
||||
if !strings.Contains(normalized, "-") {
|
||||
if len(normalized) == 1 {
|
||||
if _, ok := slotRegistry[normalized[0]]; ok {
|
||||
return []byte{normalized[0]}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Valid IDs are exact sequences of "{slot}-0" segments, e.g.
|
||||
// "H-0" or "L-0-C-0". Any malformed segment invalidates the whole ID.
|
||||
parts := strings.Split(id, "-")
|
||||
parts := strings.Split(normalized, "-")
|
||||
if len(parts)%2 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ func TestParseBlockID_ExtractsSlots_Good(t *testing.T) {
|
|||
want []byte
|
||||
}{
|
||||
{"L-0-C-0", []byte{'L', 'C'}},
|
||||
{"L.0.C.0", []byte{'L', 'C'}},
|
||||
{"H", []byte{'H'}},
|
||||
{"H-0", []byte{'H'}},
|
||||
{"C-0-C-0-C-0", []byte{'C', 'C', 'C'}},
|
||||
{"", nil},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue