feat(html): add responsive variant selector helper
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
c1852f86aa
commit
c63f0a2cbe
5 changed files with 65 additions and 3 deletions
|
|
@ -165,6 +165,8 @@ html.NewResponsive().
|
|||
|
||||
Each variant renders inside a `<div data-variant="name">` container. Variants render in insertion order. CSS media queries or JavaScript can target these containers for show/hide logic.
|
||||
|
||||
`VariantSelector(name)` returns a CSS attribute selector for a specific responsive variant, making stylesheet targeting less error-prone than hand-writing the attribute selector repeatedly.
|
||||
|
||||
`Responsive` implements `Node`, so it can be passed to `Render()` or `Imprint()`. The `Variant()` method accepts `*Layout` specifically, not arbitrary `Node` values.
|
||||
|
||||
Each variant maintains independent block ID namespaces -- nesting a layout inside a responsive variant does not conflict with the same layout structure in another variant.
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ These are not regressions; they are design choices or deferred work recorded for
|
|||
|
||||
5. **TypeScript definitions not generated.** `codegen.GenerateBundle()` produces JS only. A `.d.ts` companion would benefit TypeScript consumers of the generated Web Components.
|
||||
|
||||
6. **No CSS scoping helper.** Responsive variants are identified by `data-variant` attributes. Targeting them from CSS requires knowledge of the attribute name. An optional utility for generating scoped CSS selectors is deferred.
|
||||
6. **CSS scoping helper added.** `VariantSelector(name)` returns a reusable `data-variant` attribute selector for stylesheet targeting. The `Responsive` rendering model remains unchanged.
|
||||
|
||||
7. **Browser polyfill matrix not documented.** Closed Shadow DOM is well-supported but older browsers require polyfills. The support matrix is not documented.
|
||||
|
||||
|
|
@ -115,5 +115,6 @@ These items were captured during the WASM size reduction work and expert review
|
|||
|
||||
- **TypeScript type definitions** alongside `GenerateBundle()` for typed Web Component consumers.
|
||||
- **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()`.
|
||||
- **Responsive CSS helpers** — `VariantSelector(name)` makes `data-variant` targeting explicit and reusable in stylesheets.
|
||||
- **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.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
|
|||
|------|---------|
|
||||
| `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) |
|
||||
| `responsive.go` | Multi-variant breakpoint wrapper (`data-variant` containers) and CSS selector helper |
|
||||
| `context.go` | Rendering context: identity, locale, entitlements, i18n service |
|
||||
| `render.go` | `Render()` convenience function |
|
||||
| `path.go` | `ParseBlockID()` for decoding `data-block` path attributes |
|
||||
|
|
@ -56,7 +56,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
|
|||
|
||||
**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`.
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**Grammar pipeline** -- Server-side only. `Imprint()` renders a node tree to HTML, strips tags, tokenises the plain text via `go-i18n/reversal`, and returns a `GrammarImprint` for semantic analysis. `CompareVariants()` computes pairwise similarity scores across responsive variants.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
package html
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Compile-time interface check.
|
||||
var _ Node = (*Responsive)(nil)
|
||||
|
||||
|
|
@ -56,3 +61,41 @@ func (r *Responsive) Render(ctx *Context) string {
|
|||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// VariantSelector returns a CSS attribute selector for a responsive variant.
|
||||
// Usage example: selector := VariantSelector("desktop")
|
||||
func VariantSelector(name string) string {
|
||||
return `[data-variant="` + escapeCSSString(name) + `"]`
|
||||
}
|
||||
|
||||
func escapeCSSString(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\\', '"':
|
||||
b.WriteByte('\\')
|
||||
b.WriteRune(r)
|
||||
case '\n':
|
||||
b.WriteString(`\A `)
|
||||
case '\r':
|
||||
b.WriteString(`\D `)
|
||||
case '\f':
|
||||
b.WriteString(`\C `)
|
||||
case '\t':
|
||||
b.WriteString(`\9 `)
|
||||
default:
|
||||
if r < 0x20 || r == 0x7f {
|
||||
b.WriteByte('\\')
|
||||
b.WriteString(strings.ToUpper(strconv.FormatInt(int64(r), 16)))
|
||||
b.WriteByte(' ')
|
||||
continue
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,3 +110,19 @@ func TestResponsive_Render_NilContext_Good(t *testing.T) {
|
|||
t.Fatalf("responsive.Render(nil) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariantSelector_Good(t *testing.T) {
|
||||
got := VariantSelector("desktop")
|
||||
want := `[data-variant="desktop"]`
|
||||
if got != want {
|
||||
t.Fatalf("VariantSelector(%q) = %q, want %q", "desktop", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariantSelector_Escapes_Good(t *testing.T) {
|
||||
got := VariantSelector("desk\"top\\wide")
|
||||
want := `[data-variant="desk\"top\\wide"]`
|
||||
if got != want {
|
||||
t.Fatalf("VariantSelector escaping = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue