Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions src/MudBlazor.UnitTests/Components/DataGridTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5164,6 +5164,158 @@ public async Task DataGridSelectColumn()
selectAllCheckboxes[1].Instance.ReadValue.Should().BeFalse();
}

[Test]
[SetCulture("")]
[SetUICulture("")]
public void SelectColumn_DefaultAriaLabels_AreApplied()
{
var items = new List<int> { 1, 2 };

var comp = Context.Render<MudDataGrid<int>>(parameters => parameters
.Add(p => p.Items, items)
.Add(p => p.MultiSelection, true)
.Add(p => p.Columns, builder =>
{
builder.OpenComponent<SelectColumn<int>>(0);
builder.CloseComponent();
builder.OpenComponent<PropertyColumn<int, int>>(1);
builder.AddAttribute(2, nameof(PropertyColumn<int, int>.Property), (Expression<Func<int, int>>)(x => x));
builder.CloseComponent();
}));

var headerCheckbox = comp.Find("thead .mud-checkbox input");
headerCheckbox.GetAttribute("aria-label").Should().Be("Select all rows");

var rowCheckboxes = comp.FindAll("tbody .mud-checkbox input");
rowCheckboxes[0].GetAttribute("aria-label").Should().Be("Select row 1");
rowCheckboxes[1].GetAttribute("aria-label").Should().Be("Select row 2");
}

[Test]
public void DataGrid_WithoutSelectColumn_DoesNotRenderAriaSelected()
{
var items = new List<int> { 1, 2 };

var comp = Context.Render<MudDataGrid<int>>(parameters => parameters
.Add(p => p.Items, items)
.Add(p => p.MultiSelection, true)
.Add(p => p.Columns, builder =>
{
builder.OpenComponent<PropertyColumn<int, int>>(0);
builder.AddAttribute(1, nameof(PropertyColumn<int, int>.Property), (Expression<Func<int, int>>)(x => x));
builder.CloseComponent();
}));

var rows = comp.FindAll("tbody tr");
rows[0].HasAttribute("aria-selected").Should().BeFalse();
rows[1].HasAttribute("aria-selected").Should().BeFalse();
}

[Test]
public void SelectColumn_CustomAriaLabels_OverrideDefaults()
{
var items = new List<TestDataItem>
{
new() { Id = 1, Name = "First" },
new() { Id = 2, Name = "Second" }
};

var comp = Context.Render<MudDataGrid<TestDataItem>>(parameters => parameters
.Add(p => p.Items, items)
.Add(p => p.MultiSelection, true)
.Add(p => p.Columns, builder =>
{
builder.OpenComponent<SelectColumn<TestDataItem>>(0);
builder.AddAttribute(1, nameof(SelectColumn<TestDataItem>.RowCheckboxAriaLabelFunc), (Func<TestDataItem, int, string>)((item, index) => $"{item.Name} row {index + 1}"));
builder.AddAttribute(2, nameof(SelectColumn<TestDataItem>.SelectAllAriaLabel), "Pick every row");
builder.CloseComponent();
builder.OpenComponent<PropertyColumn<TestDataItem, string>>(3);
builder.AddAttribute(4, nameof(PropertyColumn<TestDataItem, string>.Property), (Expression<Func<TestDataItem, string>>)(x => x.Name));
builder.CloseComponent();
}));

var headerCheckbox = comp.Find("thead .mud-checkbox input");
headerCheckbox.GetAttribute("aria-label").Should().Be("Pick every row");

var rowCheckboxes = comp.FindAll("tbody .mud-checkbox input");
rowCheckboxes[0].GetAttribute("aria-label").Should().Be("First row 1");
rowCheckboxes[1].GetAttribute("aria-label").Should().Be("Second row 2");
}

[Test]
public void SelectColumn_CustomAriaLabelledBy_ReplacesDefaultLabels()
{
var items = new List<TestDataItem>
{
new() { Id = 1, Name = "First" },
new() { Id = 2, Name = "Second" }
};

var comp = Context.Render<MudDataGrid<TestDataItem>>(parameters => parameters
.Add(p => p.Items, items)
.Add(p => p.MultiSelection, true)
.Add(p => p.Columns, builder =>
{
builder.OpenComponent<SelectColumn<TestDataItem>>(0);
builder.AddAttribute(1, nameof(SelectColumn<TestDataItem>.RowCheckboxAriaLabelledByFunc), (Func<TestDataItem, int, string>)((item, index) => $"row-label-{index}-{item.Id}"));
builder.AddAttribute(2, nameof(SelectColumn<TestDataItem>.SelectAllAriaLabelledBy), "header-label");
builder.CloseComponent();
builder.OpenComponent<PropertyColumn<TestDataItem, string>>(3);
builder.AddAttribute(4, nameof(PropertyColumn<TestDataItem, string>.Property), (Expression<Func<TestDataItem, string>>)(x => x.Name));
builder.CloseComponent();
}));

var headerCheckbox = comp.Find("thead .mud-checkbox input");
headerCheckbox.GetAttribute("aria-labelledby").Should().Be("header-label");
headerCheckbox.HasAttribute("aria-label").Should().BeFalse();

var rowCheckboxes = comp.FindAll("tbody .mud-checkbox input");
rowCheckboxes[0].GetAttribute("aria-labelledby").Should().Be("row-label-0-1");
rowCheckboxes[0].HasAttribute("aria-label").Should().BeFalse();
rowCheckboxes[1].GetAttribute("aria-labelledby").Should().Be("row-label-1-2");
rowCheckboxes[1].HasAttribute("aria-label").Should().BeFalse();
}

[Test]
public async Task SelectColumn_SetsAriaSelectedOnRows()
{
var items = new List<int> { 1, 2 };

var comp = Context.Render<MudDataGrid<int>>(parameters => parameters
.Add(p => p.Items, items)
.Add(p => p.MultiSelection, true)
.Add(p => p.Columns, builder =>
{
builder.OpenComponent<SelectColumn<int>>(0);
builder.CloseComponent();
builder.OpenComponent<PropertyColumn<int, int>>(1);
builder.AddAttribute(2, nameof(PropertyColumn<int, int>.Property), (Expression<Func<int, int>>)(x => x));
builder.CloseComponent();
}));

List<IElement> Rows() => comp.FindAll("tbody tr").ToList();
List<IElement> RowCheckboxes() => comp.FindAll("tbody .mud-checkbox input").ToList();
IElement HeaderCheckbox() => comp.Find("thead .mud-checkbox input");

Rows()[0].GetAttribute("aria-selected").Should().Be("false");
Rows()[1].GetAttribute("aria-selected").Should().Be("false");

await RowCheckboxes()[0].ChangeAsync(true);

Rows()[0].GetAttribute("aria-selected").Should().Be("true");
Rows()[1].GetAttribute("aria-selected").Should().Be("false");

await HeaderCheckbox().ChangeAsync(true);

Rows()[0].GetAttribute("aria-selected").Should().Be("true");
Rows()[1].GetAttribute("aria-selected").Should().Be("true");

await HeaderCheckbox().ChangeAsync(false);

Rows()[0].GetAttribute("aria-selected").Should().Be("false");
Rows()[1].GetAttribute("aria-selected").Should().Be("false");
}

[Test]
public async Task FilterDefinitionTestHasFilterProperty()
{
Expand Down
7 changes: 6 additions & 1 deletion src/MudBlazor/Components/DataGrid/Cell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ internal object? ComputedValue
#endregion

public Cell(MudDataGrid<T> dataGrid, Column<T> column, T item)
: this(dataGrid, column, item, -1)
{
}

public Cell(MudDataGrid<T> dataGrid, Column<T> column, T item, int rowIndex)
{
_dataGrid = dataGrid;
_column = column;
Expand All @@ -55,7 +60,7 @@ public Cell(MudDataGrid<T> dataGrid, Column<T> column, T item)
OnStartedEditingItem();

// Create the CellContext
_cellContext = new CellContext<T>(_dataGrid, _item);
_cellContext = rowIndex >= 0 ? new CellContext<T>(_dataGrid, _item, rowIndex) : new CellContext<T>(_dataGrid, _item);
}

public async Task StringValueChangedAsync(string? value)
Expand Down
17 changes: 17 additions & 0 deletions src/MudBlazor/Components/DataGrid/CellContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class CellContext<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTy
/// </summary>
public bool Open => OpenHierarchies.Contains(Item);

/// <summary>
/// The zero-based index of the row displayed in the grid, or <c>-1</c> when unavailable.
/// </summary>
public int RowIndex { get; private set; } = -1;

/// <summary>
/// Creates a new instance.
/// </summary>
Expand All @@ -56,6 +61,18 @@ public CellContext(MudDataGrid<T> dataGrid, T item)
};
}

/// <summary>
/// Creates a new instance with an explicit row index.
/// </summary>
/// <param name="dataGrid">The data grid which owns this context.</param>
/// <param name="item">The item displayed in the cell.</param>
/// <param name="rowIndex">The zero-based row index.</param>
public CellContext(MudDataGrid<T> dataGrid, T item, int rowIndex)
: this(dataGrid, item)
{
RowIndex = rowIndex;
}

/// <summary>
/// Represents behaviors which can be performed on a cell.
/// </summary>
Expand Down
22 changes: 19 additions & 3 deletions src/MudBlazor/Components/DataGrid/DataGridVirtualizeRow.razor
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
var rowStyle = new StyleBuilder().AddStyle(DataGrid.RowStyle).AddStyle(DataGrid.RowStyleFunc?.Invoke(itemBag.Item, itemBag.Index)).Build();
}
<tr class="mud-table-row @rowClass" Style="@rowStyle" @key="itemBag.Item"
@attributes="GetRowAttributes(itemBag.Item)"
@onclick="@((args) => RowClick.InvokeAsync((args, itemBag.Item, itemBag.Index)))"
@oncontextmenu="@((args) => ContextRowClick.InvokeAsync((args, itemBag.Item, itemBag.Index)))"
@oncontextmenu:preventDefault="@(DataGrid.RowContextMenuClick.HasDelegate ? true : false)">
Expand All @@ -65,7 +66,7 @@
{
if (!column.HiddenState.Value)
{
@DataGrid.Cell(column, itemBag.Item)
@DataGrid.Cell(column, itemBag.Item, itemBag.Index)
}
}
</CascadingValue>
Expand All @@ -74,13 +75,13 @@
{
@if (DataGrid.ChildRowRenderer != null)
{
@DataGrid.ChildRowRenderer(new CellContext<T>(DataGrid, itemBag.Item))
@DataGrid.ChildRowRenderer(new CellContext<T>(DataGrid, itemBag.Item, itemBag.Index))
}
@if (DataGrid.ChildRowContent != null)
{
<tr class="mud-table-row">
<td class="mud-table-cell mud-table-child-content" colspan="1000">
@DataGrid.ChildRowContent(new CellContext<T>(DataGrid, itemBag.Item))
@DataGrid.ChildRowContent(new CellContext<T>(DataGrid, itemBag.Item, itemBag.Index))
</td>
</tr>
}
Expand All @@ -96,3 +97,18 @@
</tr>
</NoRecordsContent>
</MudVirtualize>

@code {
private Dictionary<string, object?>? GetRowAttributes(T item)
{
if (!DataGrid.HasSelectColumn)
{
return null;
}

return new Dictionary<string, object?>(1)
{
["aria-selected"] = DataGrid.Selection.Contains(item).ToString().ToLowerInvariant()
};
}
}
4 changes: 2 additions & 2 deletions src/MudBlazor/Components/DataGrid/MudDataGrid.razor
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,10 @@
</text>;


internal RenderFragment Cell(Column<T> column, T item) =>
internal RenderFragment Cell(Column<T> column, T item, int rowIndex) =>
@<text>
@{
var cell = new Cell<T>(this, column, item);
var cell = new Cell<T>(this, column, item, rowIndex);
}
<td data-label="@column.Title" class="@cell.ComputedClass" style="@cell.ComputedStyle">
@if (column.Editable && !ReadOnly && EditMode == DataGridEditMode.Cell)
Expand Down
2 changes: 2 additions & 0 deletions src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ private Task ItemUpdatedAsync(MudItemDropInfo<Column<T>> dropItem)
/// </summary>
public readonly List<Column<T>> RenderedColumns = new List<Column<T>>();

internal bool HasSelectColumn => RenderedColumns.OfType<SelectColumn<T>>().Any();

internal T? _editingItem;

internal T? _editingSourceItem;
Expand Down
9 changes: 6 additions & 3 deletions src/MudBlazor/Components/DataGrid/SelectColumn.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
return @<MudCheckBox T="bool?"
Size="@Size"
Value="@context.IsAllSelected"
ValueChanged="@context.Actions.SetSelectAllAsync" />;
ValueChanged="@context.Actions.SetSelectAllAsync"
UserAttributes="@GetSelectAllAttributes()" />;
}

return null!;
Expand All @@ -21,7 +22,8 @@
Size="@Size"
Value="@context.Selected"
ValueChanged="@context.Actions.SetSelectedItemAsync"
Disabled="@(DisabledFunc?.Invoke(context.Item) ?? false)" />;
Disabled="@(DisabledFunc?.Invoke(context.Item) ?? false)"
UserAttributes="@GetRowCheckboxAttributes(context)" />;

private RenderFragment<FooterContext<T>>? GetSelectFooterTemplate() => context =>
{
Expand All @@ -30,7 +32,8 @@
return @<MudCheckBox T="bool?"
Size="@Size"
Value="@context.IsAllSelected"
ValueChanged="@context.Actions.SetSelectAllAsync" />;
ValueChanged="@context.Actions.SetSelectAllAsync"
UserAttributes="@GetSelectAllAttributes()" />;
}

return null!;
Expand Down
Loading
Loading