fix(html): scope selector lists correctly
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
967182a676
commit
b5d170817c
2 changed files with 72 additions and 1 deletions
|
|
@ -81,7 +81,7 @@ func ScopeVariant(name, selector string) string {
|
|||
return scope
|
||||
}
|
||||
|
||||
parts := strings.Split(selector, ",")
|
||||
parts := splitSelectorList(selector)
|
||||
scoped := make([]string, 0, len(parts))
|
||||
for i := range parts {
|
||||
part := strings.TrimSpace(parts[i])
|
||||
|
|
@ -96,6 +96,69 @@ func ScopeVariant(name, selector string) string {
|
|||
return strings.Join(scoped, ", ")
|
||||
}
|
||||
|
||||
// splitSelectorList splits a CSS selector list on top-level commas only.
|
||||
// Commas inside brackets, parentheses, braces, or quoted strings are preserved.
|
||||
func splitSelectorList(selector string) []string {
|
||||
if selector == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
parts := make([]string, 0, 1)
|
||||
var b strings.Builder
|
||||
var quote rune
|
||||
depthParen := 0
|
||||
depthBracket := 0
|
||||
depthBrace := 0
|
||||
|
||||
for _, r := range selector {
|
||||
switch {
|
||||
case quote != 0:
|
||||
b.WriteRune(r)
|
||||
if r == quote {
|
||||
quote = 0
|
||||
} else if r == '\\' {
|
||||
// Keep escaped characters inside quoted strings intact.
|
||||
continue
|
||||
}
|
||||
case r == '"' || r == '\'':
|
||||
quote = r
|
||||
b.WriteRune(r)
|
||||
case r == '(':
|
||||
depthParen++
|
||||
b.WriteRune(r)
|
||||
case r == ')':
|
||||
if depthParen > 0 {
|
||||
depthParen--
|
||||
}
|
||||
b.WriteRune(r)
|
||||
case r == '[':
|
||||
depthBracket++
|
||||
b.WriteRune(r)
|
||||
case r == ']':
|
||||
if depthBracket > 0 {
|
||||
depthBracket--
|
||||
}
|
||||
b.WriteRune(r)
|
||||
case r == '{':
|
||||
depthBrace++
|
||||
b.WriteRune(r)
|
||||
case r == '}':
|
||||
if depthBrace > 0 {
|
||||
depthBrace--
|
||||
}
|
||||
b.WriteRune(r)
|
||||
case r == ',' && depthParen == 0 && depthBracket == 0 && depthBrace == 0:
|
||||
parts = append(parts, b.String())
|
||||
b.Reset()
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
parts = append(parts, b.String())
|
||||
return parts
|
||||
}
|
||||
|
||||
// responsive.go: Variant adds a named layout variant (e.g., "desktop", "tablet", "mobile").
|
||||
// Example: r.Variant("mobile", NewLayout("C").C(Raw("body"))).
|
||||
// Variants render in insertion order.
|
||||
|
|
|
|||
|
|
@ -215,3 +215,11 @@ func TestScopeVariant_IgnoresEmptySelectorSegments(t *testing.T) {
|
|||
t.Fatalf("ScopeVariant should skip empty selector segments = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScopeVariant_PreservesNestedCommas(t *testing.T) {
|
||||
got := ScopeVariant("desktop", `:is(.nav, .sidebar), .footer`)
|
||||
want := `[data-variant="desktop"] :is(.nav, .sidebar), [data-variant="desktop"] .footer`
|
||||
if got != want {
|
||||
t.Fatalf("ScopeVariant should preserve nested commas = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue