fix(html): dedupe ARIA relationship tokens
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
daaae16493
commit
9230d3b66c
2 changed files with 61 additions and 7 deletions
38
node.go
38
node.go
|
|
@ -226,7 +226,7 @@ func AriaLabel(n Node, label string) Node {
|
|||
// Example: AriaDescribedBy(El("input"), "help-text", "error-text").
|
||||
// Multiple IDs are joined with spaces, matching the HTML attribute format.
|
||||
func AriaDescribedBy(n Node, ids ...string) Node {
|
||||
if value := joinNonEmpty(ids...); value != "" {
|
||||
if value := joinUniqueNonEmpty(ids...); value != "" {
|
||||
return Attr(n, "aria-describedby", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -236,7 +236,7 @@ func AriaDescribedBy(n Node, ids ...string) Node {
|
|||
// Example: AriaLabelledBy(El("section"), "section-title").
|
||||
// Multiple IDs are joined with spaces, matching the HTML attribute format.
|
||||
func AriaLabelledBy(n Node, ids ...string) Node {
|
||||
if value := joinNonEmpty(ids...); value != "" {
|
||||
if value := joinUniqueNonEmpty(ids...); value != "" {
|
||||
return Attr(n, "aria-labelledby", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -246,7 +246,7 @@ func AriaLabelledBy(n Node, ids ...string) Node {
|
|||
// Example: AriaControls(El("button"), "menu-panel").
|
||||
// Multiple IDs are joined with spaces, matching the HTML attribute format.
|
||||
func AriaControls(n Node, ids ...string) Node {
|
||||
if value := joinNonEmpty(ids...); value != "" {
|
||||
if value := joinUniqueNonEmpty(ids...); value != "" {
|
||||
return Attr(n, "aria-controls", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -266,7 +266,7 @@ func AriaHasPopup(n Node, popup string) Node {
|
|||
// Example: AriaOwns(El("div"), "owned-panel").
|
||||
// Multiple IDs are joined with spaces, matching the HTML attribute format.
|
||||
func AriaOwns(n Node, ids ...string) Node {
|
||||
if value := joinNonEmpty(ids...); value != "" {
|
||||
if value := joinUniqueNonEmpty(ids...); value != "" {
|
||||
return Attr(n, "aria-owns", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -276,7 +276,7 @@ func AriaOwns(n Node, ids ...string) Node {
|
|||
// Example: AriaKeyShortcuts(El("button"), "Ctrl+S", "Meta+S").
|
||||
// Multiple shortcuts are joined with spaces, matching the HTML attribute format.
|
||||
func AriaKeyShortcuts(n Node, shortcuts ...string) Node {
|
||||
if value := joinNonEmpty(shortcuts...); value != "" {
|
||||
if value := joinUniqueNonEmpty(shortcuts...); value != "" {
|
||||
return Attr(n, "aria-keyshortcuts", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -334,7 +334,7 @@ func AriaDescription(n Node, description string) Node {
|
|||
// Example: AriaDetails(El("input"), "field-help").
|
||||
// Multiple IDs are joined with spaces, matching the HTML attribute format.
|
||||
func AriaDetails(n Node, ids ...string) Node {
|
||||
if value := joinNonEmpty(ids...); value != "" {
|
||||
if value := joinUniqueNonEmpty(ids...); value != "" {
|
||||
return Attr(n, "aria-details", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -344,7 +344,7 @@ func AriaDetails(n Node, ids ...string) Node {
|
|||
// Example: AriaErrorMessage(El("input"), "field-error").
|
||||
// Multiple IDs are joined with spaces, matching the HTML attribute format.
|
||||
func AriaErrorMessage(n Node, ids ...string) Node {
|
||||
if value := joinNonEmpty(ids...); value != "" {
|
||||
if value := joinUniqueNonEmpty(ids...); value != "" {
|
||||
return Attr(n, "aria-errormessage", value)
|
||||
}
|
||||
return n
|
||||
|
|
@ -637,6 +637,30 @@ func joinNonEmpty(parts ...string) string {
|
|||
return strings.Join(filtered, " ")
|
||||
}
|
||||
|
||||
func joinUniqueNonEmpty(parts ...string) string {
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(parts))
|
||||
filtered := make([]string, 0, len(parts))
|
||||
for i := range parts {
|
||||
part := strings.TrimSpace(parts[i])
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[part]; ok {
|
||||
continue
|
||||
}
|
||||
seen[part] = struct{}{}
|
||||
filtered = append(filtered, part)
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(filtered, " ")
|
||||
}
|
||||
|
||||
func trimmedNonEmpty(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
|
|
|
|||
30
node_test.go
30
node_test.go
|
|
@ -295,6 +295,16 @@ func TestAriaDescribedByHelper_IgnoresWhitespaceIDs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAriaDescribedByHelper_DeduplicatesIDs(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := AriaDescribedBy(El("input"), "hint-1", "hint-1", "hint-2", "hint-2")
|
||||
got := node.Render(ctx)
|
||||
want := `<input aria-describedby="hint-1 hint-2">`
|
||||
if got != want {
|
||||
t.Errorf("AriaDescribedBy() with duplicate IDs = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAriaLabelledByHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := AriaLabelledBy(El("input"), "label-1", "label-2")
|
||||
|
|
@ -315,6 +325,16 @@ func TestAriaLabelledByHelper_IgnoresEmptyIDs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAriaLabelledByHelper_DeduplicatesIDs(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := AriaLabelledBy(El("input"), "label-1", "label-1", "label-2", "label-2")
|
||||
got := node.Render(ctx)
|
||||
want := `<input aria-labelledby="label-1 label-2">`
|
||||
if got != want {
|
||||
t.Errorf("AriaLabelledBy() with duplicate IDs = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAriaControlsHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := AriaControls(El("button", Raw("menu")), "menu-panel", "shortcut-hints")
|
||||
|
|
@ -335,6 +355,16 @@ func TestAriaControlsHelper_IgnoresEmptyIDs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAriaControlsHelper_DeduplicatesIDs(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := AriaControls(El("button", Raw("menu")), "menu-panel", "menu-panel", "shortcut-hints")
|
||||
got := node.Render(ctx)
|
||||
want := `<button aria-controls="menu-panel shortcut-hints">menu</button>`
|
||||
if got != want {
|
||||
t.Errorf("AriaControls() with duplicate IDs = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAriaHasPopupHelper(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
node := AriaHasPopup(El("button", Raw("menu")), "menu")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue