Skip to content

Commit 6a7efdc

Browse files
authored
Improve MudListItemExtended Accessibility (#616)
* Update accessibility on MudListItemExtended with role, aria-selected and aria-label with new AccessibleName property. * Improved checkbox aria-label identification * Update other checkboxes
1 parent 38ff73a commit 6a7efdc

File tree

4 files changed

+83
-12
lines changed

4 files changed

+83
-12
lines changed

docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/CodeBeam.MudBlazor.Extensions/Components/ListExtended/MudListItemExtended.razor

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55

66
@if (IsVisible)
77
{
8-
<div tabindex="0" @attributes="UserAttributes" id="@ItemId" class="@Classname" @onclick="@(((MudListExtended?.Clickable == true || NestedList != null) && IsFunctional == false) ? OnClickHandler : OnlyOnClick)" @onclick:stopPropagation="@OnClickStopPropagation" style="@Style">
8+
<div tabindex="0" @attributes="UserAttributes" id="@ItemId" class="@Classname"
9+
role="option"
10+
aria-selected="@(_selected ? "true" : "false")"
11+
aria-label="@AccessibleName"
12+
@onclick="@(((MudListExtended?.Clickable == true || NestedList != null) && IsFunctional == false) ? OnClickHandler : OnlyOnClick)" @onclick:stopPropagation="@OnClickStopPropagation" style="@Style">
913

1014
@if (MudListExtended?.ItemDisabledTemplate != null && GetDisabledStatus() == true)
11-
{
15+
{
1216
@MudListExtended.ItemDisabledTemplate(this)
1317
}
1418
else if (MudListExtended?.ItemSelectedTemplate != null && IsSelected == true)
@@ -27,15 +31,18 @@
2731
<div class="@MultiSelectClassName">
2832
@if (OverrideMultiSelectionComponent == null ? MudListExtended?.MultiSelectionComponent == MultiSelectionComponent.CheckBox : OverrideMultiSelectionComponent.Value == MultiSelectionComponent.CheckBox)
2933
{
30-
<MudCheckBox Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" Dense="true" />
34+
<MudCheckBox Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" Dense="true"
35+
aria-label="@($"Select {AccessibleName}")" />
3136
}
3237
else if (OverrideMultiSelectionComponent == null ? MudListExtended?.MultiSelectionComponent == MultiSelectionComponent.Switch : OverrideMultiSelectionComponent.Value == MultiSelectionComponent.Switch)
3338
{
34-
<MudSwitch Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true" />
39+
<MudSwitch Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true"
40+
aria-label="@($"Select {AccessibleName}")" />
3541
}
3642
else if (OverrideMultiSelectionComponent == null ? MudListExtended?.MultiSelectionComponent == MultiSelectionComponent.SwitchM3 : OverrideMultiSelectionComponent.Value == MultiSelectionComponent.SwitchM3)
3743
{
38-
<MudSwitchM3 Class="mr-4" Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true" />
44+
<MudSwitchM3 Class="mr-4" Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true"
45+
aria-label="@($"Select {AccessibleName}")" />
3946
}
4047
</div>
4148
}
@@ -75,15 +82,18 @@
7582
<div class="@MultiSelectClassName">
7683
@if (OverrideMultiSelectionComponent == null ? MudListExtended?.MultiSelectionComponent == MultiSelectionComponent.CheckBox : OverrideMultiSelectionComponent.Value == MultiSelectionComponent.CheckBox)
7784
{
78-
<MudCheckBox Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" Dense="true" />
85+
<MudCheckBox Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" Dense="true"
86+
aria-label="@($"Select {AccessibleName}")" />
7987
}
8088
else if (OverrideMultiSelectionComponent == null ? MudListExtended?.MultiSelectionComponent == MultiSelectionComponent.Switch : OverrideMultiSelectionComponent.Value == MultiSelectionComponent.Switch)
8189
{
82-
<MudSwitch Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true" />
90+
<MudSwitch Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true"
91+
aria-label="@($"Select {AccessibleName}")" />
8392
}
8493
else if (OverrideMultiSelectionComponent == null ? MudListExtended?.MultiSelectionComponent == MultiSelectionComponent.SwitchM3 : OverrideMultiSelectionComponent.Value == MultiSelectionComponent.SwitchM3)
8594
{
86-
<MudSwitchM3 Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true" />
95+
<MudSwitchM3 Color="@(MudListExtended?.Color ?? Color.Default)" Disabled="@GetDisabledStatus()" @bind-Value="_selected" @onclick="OnClickHandler" StopClickPropagation="true"
96+
aria-label="@($"Select {AccessibleName}")" />
8797
}
8898
</div>
8999
}

src/CodeBeam.MudBlazor.Extensions/Components/ListExtended/MudListItemExtended.razor.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
using System.Windows.Input;
2-
using Microsoft.AspNetCore.Components;
1+
using Microsoft.AspNetCore.Components;
32
using Microsoft.AspNetCore.Components.Web;
4-
using MudBlazor.Extensions;
53
using MudBlazor.Utilities;
64
using MudBlazor;
75

@@ -59,7 +57,29 @@ public partial class MudListItemExtended<T> : MudComponentBase, IDisposable
5957
/// <summary>
6058
///
6159
/// </summary>
62-
protected internal string? ItemId { get; } = "listitem_" + Guid.NewGuid().ToString().Substring(0, 8);
60+
protected internal string? ItemId { get; } = string.Concat("listitem_", Guid.NewGuid().ToString().AsSpan(0, 8));
61+
62+
/// <summary>
63+
/// The accessible name used for the item. Prefer Text, then SecondaryText, then Value.ToString().
64+
/// If the parent list has a ToStringFunc use that for Value formatting.
65+
/// </summary>
66+
protected string AccessibleName
67+
{
68+
get
69+
{
70+
if (!string.IsNullOrWhiteSpace(Text))
71+
return Text!;
72+
if (!string.IsNullOrWhiteSpace(SecondaryText))
73+
return SecondaryText!;
74+
if (Value != null)
75+
{
76+
if (MudListExtended?.ToStringFunc != null)
77+
return MudListExtended.ToStringFunc(Value) ?? Value.ToString() ?? string.Empty;
78+
return Value.ToString() ?? string.Empty;
79+
}
80+
return string.Empty;
81+
}
82+
}
6383

6484
/// <summary>
6585
/// Functional items does not hold values. If a value set on Functional item, it ignores by the MudList. They can not count on Items list (they count on AllItems), cannot be subject of keyboard navigation and selection.

tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/SelectExtendedTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,41 @@ public void SelectLabelFor()
2929
label[0].Attributes.GetNamedItem("for")?.Value.Should().Be("selectLabelTest");
3030
}
3131

32+
[Test]
33+
public void Select_Items_Should_Expose_Accessible_Role_And_Label()
34+
{
35+
var comp = Context.Render<SelectTest1>();
36+
comp.Find("div.mud-input-control").Click();
37+
comp.WaitForAssertion(() => comp.FindAll("div.mud-list-item-extended").Count.Should().BeGreaterThan(0));
38+
39+
var items = comp.FindAll("div.mud-list-item-extended").ToArray();
40+
var item = items[1];
41+
item.GetAttribute("role").Should().Be("option");
42+
item.GetAttribute("aria-selected").Should().Be("false");
43+
var ariaLabel = item.GetAttribute("aria-label");
44+
ariaLabel.Should().NotBeNullOrEmpty();
45+
ariaLabel.Trim().Should().Be("2");
46+
}
47+
48+
[Test]
49+
public void MultiSelect_Items_Should_Expose_Checkbox_AriaLabel_And_TestId()
50+
{
51+
var comp = Context.Render<MultiSelectTest1>();
52+
comp.Find("div.mud-input-control").Click();
53+
comp.WaitForAssertion(() => comp.FindAll("div.mud-list-item-extended").Count.Should().BeGreaterThan(0));
54+
55+
var items = comp.FindAll("div.mud-list-item-extended").ToArray();
56+
var item = items[1];
57+
item.GetAttribute("role").Should().Be("option");
58+
var checkbox = item.QuerySelector("input[type=checkbox]");
59+
checkbox.Should().NotBeNull();
60+
var optionAria = item.GetAttribute("aria-label");
61+
optionAria.Should().NotBeNullOrEmpty();
62+
var checkboxAria = checkbox.GetAttribute("aria-label");
63+
checkboxAria.Should().NotBeNullOrEmpty();
64+
checkboxAria.Trim().Should().Be($"Select {optionAria.Trim()}");
65+
}
66+
3267
// Note: MudSelect doesn't guaranteed the consequences of changing Value if MultiSelection is true for now.
3368
// When this feature will add, just uncomment the testcase to test it. No need to write new test.
3469
[Test]

0 commit comments

Comments
 (0)