diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index 9d717562c4..02aa1d20ff 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -1245,6 +1245,36 @@ Gets a reference to the enclosing . + + + Event callback for when the row is clicked. + + + + + + + Event callback for when the key is pressed on a row. + + + + + + + + Event callback for when the cell is clicked. + + + + + + + Event callback for when the key is pressed on a cell. + + + + + Overridden by derived components to provide rendering logic for the column's cells. @@ -1558,6 +1588,36 @@ Allows to clear the selection. + + + Select on Unselect an item when the row is clicked. + + + + + + + Select on Unselect an item when the navigation keys are pressed. + + + + + + + + Select on Unselect an item when the cell is clicked. + + + + + + + Select on Unselect an item when the navigation keys are pressed. + + + + + @@ -1585,6 +1645,9 @@ + + + Holds the name of a property and the direction to sort by. @@ -1773,11 +1836,6 @@ A default fragment is used if loading content is not specified. - - - Gets the first (optional) SelectColumn - - Constructs an instance of . @@ -5450,6 +5508,9 @@ + + + diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 40da078af5..c2796d96b7 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web.Virtualization; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; @@ -11,48 +12,56 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// The type of data represented by each row in the grid. public abstract partial class ColumnBase { - [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; + [CascadingParameter] + internal InternalGridContext InternalGridContext { get; set; } = default!; /// /// Gets or sets the title text for the column. /// This is rendered automatically if is not used. /// - [Parameter] public string? Title { get; set; } + [Parameter] + public string? Title { get; set; } /// /// Gets or sets the an optional CSS class name. /// If specified, this is included in the class attribute of header and grid cells /// for this column. /// - [Parameter] public string? Class { get; set; } + [Parameter] + public string? Class { get; set; } /// /// Gets or sets an optional CSS style specification. /// If specified, this is included in the style attribute of header and grid cells /// for this column. /// - [Parameter] public string? Style { get; set; } + [Parameter] + public string? Style { get; set; } /// /// If specified, controls the justification of header and grid cells for this column. /// - [Parameter] public Align Align { get; set; } + [Parameter] + public Align Align { get; set; } /// /// If true, generates a title and aria-label attribute for the cell contents /// - [Parameter] public bool Tooltip { get; set; } = false; + [Parameter] + public bool Tooltip { get; set; } = false; /// /// Gets or sets the value to be used as the tooltip and aria-label in this column's cells /// - [Parameter] public Func? TooltipText { get; set; } + [Parameter] + public Func? TooltipText { get; set; } /// /// Gets or sets an optional template for this column's header cell. /// If not specified, the default header template includes the along with any applicable sort indicators and options buttons. /// - [Parameter] public RenderFragment>? HeaderCellItemTemplate { get; set; } + [Parameter] + public RenderFragment>? HeaderCellItemTemplate { get; set; } /// /// If specified, indicates that this column has this associated options UI. A button to display this @@ -61,7 +70,8 @@ public abstract partial class ColumnBase /// If is used, it is left up to that template to render any relevant /// "show options" UI and invoke the grid's ). /// - [Parameter] public RenderFragment? ColumnOptions { get; set; } + [Parameter] + public RenderFragment? ColumnOptions { get; set; } /// /// Gets or sets a value indicating whether the data should be sortable by this column. @@ -70,14 +80,16 @@ public abstract partial class ColumnBase /// or is sortable by default if any /// or parameter is specified). /// - [Parameter] public bool? Sortable { get; set; } + [Parameter] + public bool? Sortable { get; set; } /// /// Gets or sets a value indicating whether the data is currently filtered by this column. /// /// The default value is false. /// - [Parameter] public bool? Filtered { get; set; } + [Parameter] + public bool? Filtered { get; set; } /// /// Gets or sets the sorting rules for a column. @@ -88,29 +100,75 @@ public abstract partial class ColumnBase /// Gets or sets the initial sort direction. /// if is true. /// - [Parameter] public SortDirection InitialSortDirection { get; set; } = default; + [Parameter] + public SortDirection InitialSortDirection { get; set; } = default; /// /// Gets or sets a value indicating whether this column should be sorted by default. /// - [Parameter] public bool IsDefaultSortColumn { get; set; } = false; + [Parameter] + public bool IsDefaultSortColumn { get; set; } = false; /// /// If specified, virtualized grids will use this template to render cells whose data has not yet been loaded. /// - [Parameter] public RenderFragment? PlaceholderTemplate { get; set; } + [Parameter] + public RenderFragment? PlaceholderTemplate { get; set; } /// /// Gets or sets the width of the column. /// Use either this or the GridTemplateColumns parameter but not both. /// Needs to be a valid CSS width value like '100px', '10%' or '0.5fr'. /// - [Parameter] public string? Width { get; set; } + [Parameter] + public string? Width { get; set; } /// /// Gets a reference to the enclosing . /// - public FluentDataGrid Grid => InternalGridContext.Grid; + internal FluentDataGrid Grid => InternalGridContext.Grid; + + /// + /// Event callback for when the row is clicked. + /// + /// + /// + protected internal virtual Task OnRowClickAsync(FluentDataGridRow row) + { + return Task.CompletedTask; + } + + /// + /// Event callback for when the key is pressed on a row. + /// + /// + /// + /// + protected internal virtual Task OnRowKeyDownAsync(FluentDataGridRow row, KeyboardEventArgs args) + { + return Task.CompletedTask; + } + + /// + /// Event callback for when the cell is clicked. + /// + /// + /// + protected internal virtual Task OnCellClickAsync(FluentDataGridCell cell) + { + return Task.CompletedTask; + } + + /// + /// Event callback for when the key is pressed on a cell. + /// + /// + /// + /// + protected internal virtual Task OnCellKeyDownAsync(FluentDataGridCell cell, KeyboardEventArgs args) + { + return Task.CompletedTask; + } /// /// Overridden by derived components to provide rendering logic for the column's cells. diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 24882ac088..2535a7c73d 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -200,13 +200,76 @@ public void ClearSelection() /// public async Task ClearSelectionAsync() { - _selectedItems.Clear(); - RefreshHeaderContent(); + ClearSelection(); await Task.CompletedTask; } + /// + /// Select on Unselect an item when the row is clicked. + /// + /// + /// + protected internal override Task OnRowClickAsync(FluentDataGridRow row) + { + if (SelectFromEntireRow == true && row.RowType == DataGridRowType.Default) + { + return AddOrRemoveSelectedItemAsync(row.Item); + } + + return Task.CompletedTask; + } + + /// + /// Select on Unselect an item when the navigation keys are pressed. + /// + /// + /// + /// + protected internal override Task OnRowKeyDownAsync(FluentDataGridRow row, KeyboardEventArgs args) + { + if (SelectFromEntireRow == true && row.RowType == DataGridRowType.Default) + { + return AddOrRemoveSelectedItemAsync(row.Item); + } + + return Task.CompletedTask; + } + + /// + /// Select on Unselect an item when the cell is clicked. + /// + /// + /// + protected internal override Task OnCellClickAsync(FluentDataGridCell cell) + { + // If the cell is a checkbox cell, add or remove the item from the selected items list. + if (SelectFromEntireRow == false && cell.CellType == DataGridCellType.Default) + { + return AddOrRemoveSelectedItemAsync(cell.Item); + } + + return Task.CompletedTask; + } + + /// + /// Select on Unselect an item when the navigation keys are pressed. + /// + /// + /// + /// + protected internal override Task OnCellKeyDownAsync(FluentDataGridCell cell, KeyboardEventArgs args) + { + // If the cell is a checkbox cell, add or remove the item from the selected items list. + if (SelectFromEntireRow == false && cell.CellType == DataGridCellType.Default) + { + return AddOrRemoveSelectedItemAsync(cell.Item); + } + + return Task.CompletedTask; + } + /// - internal async Task AddOrRemoveSelectedItemAsync(TGridItem? item) + private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) { if (item != null) { @@ -348,9 +411,10 @@ private RenderFragment GetHeaderContent() if (!SelectAllDisabled) { builder.AddAttribute(3, "OnClick", EventCallback.Factory.Create(this, OnClickAllAsync)); + builder.AddAttribute(4, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); } - builder.AddAttribute(4, "Style", "margin-left: 12px;"); - builder.AddAttribute(5, "Title", iconAllChecked == IconIndeterminate + builder.AddAttribute(5, "Style", "margin-left: 12px;"); + builder.AddAttribute(6, "Title", iconAllChecked == IconIndeterminate ? TitleAllIndeterminate : (iconAllChecked == GetIcon(true) ? TitleAllChecked : TitleAllUnchecked)); builder.CloseComponent(); @@ -377,8 +441,9 @@ private void RefreshHeaderContent() { builder.AddAttribute(1, "style", "cursor: pointer; margin-left: 12px;"); builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, OnClickAllAsync)); + builder.AddAttribute(3, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); } - builder.AddContent(3, SelectAllTemplate.Invoke(new SelectAllTemplateArgs(GetSelectAll()))); + builder.AddContent(4, SelectAllTemplate.Invoke(new SelectAllTemplateArgs(GetSelectAll()))); builder.CloseElement(); }); } @@ -451,6 +516,15 @@ internal async Task OnClickAllAsync(MouseEventArgs e) RefreshHeaderContent(); } + + /// + internal async Task OnKeyAllAsync(KeyboardEventArgs e) + { + if (KEYBOARD_SELECT_KEYS.Contains(e.Code)) + { + await OnClickAllAsync(new MouseEventArgs()); + } + } } public record SelectAllTemplateArgs(bool? AllSelected) { } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 04917057be..1a58f58228 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -178,11 +178,6 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Inject] private IJSRuntime JSRuntime { get; set; } = default!; [Inject] private IKeyCodeService KeyCodeService { get; set; } = default!; - /// - /// Gets the first (optional) SelectColumn - /// - internal IEnumerable> SelectColumns => _columns.Where(col => col is SelectColumn).Cast< SelectColumn>(); - private ElementReference? _gridReference; private Virtualize<(int, TGridItem)>? _virtualizeComponent; diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor b/src/Core/Components/DataGrid/FluentDataGridCell.razor index e94605961d..a05de6cc31 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor @@ -6,6 +6,7 @@ grid-column=@GridColumn class="@Class" style="@StyleValue" + @onkeydown="@HandleOnCellKeyDownAsync" @onclick="@HandleOnCellClickAsync" @attributes="AdditionalAttributes"> @ChildContent diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index ad3fa3cada..c81423a886 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------ using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; using Microsoft.FluentUI.AspNetCore.Components.Utilities; @@ -72,11 +73,22 @@ internal async Task HandleOnCellClickAsync() await GridContext.Grid.OnCellClick.InvokeAsync(this); } - // If the cell is a checkbox cell, add or remove the item from the selected items list. - var selectColumn = Column as SelectColumn; - if (CellType == DataGridCellType.Default && selectColumn != null && selectColumn.SelectFromEntireRow == false) + if (Column != null) { - await selectColumn.AddOrRemoveSelectedItemAsync(Item); + await Column.OnCellClickAsync(this); + } + } + + internal async Task HandleOnCellKeyDownAsync(KeyboardEventArgs e) + { + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + { + return; + } + + if (Column != null) + { + await Column.OnCellKeyDownAsync(this, e); } } diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index 3051b6f0f3..bfcd3de9e6 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -103,19 +103,18 @@ private async Task HandleOnCellFocusAsync(DataGridCellFocusEventArgs args) /// internal async Task HandleOnRowClickAsync(string rowId) { - if (Owner.Rows.TryGetValue(rowId, out var row)) + var row = GetRow(rowId); + + if (row != null && Owner.Grid.OnRowClick.HasDelegate) { - if (Owner.Grid.OnRowClick.HasDelegate) - { - await Owner.Grid.OnRowClick.InvokeAsync(row); - } + await Owner.Grid.OnRowClick.InvokeAsync(row); + } - if (row != null && row.RowType == DataGridRowType.Default) + if (row != null && row.RowType == DataGridRowType.Default) + { + foreach (var column in Owner.Grid._columns) { - foreach (var selColumn in Owner.Grid.SelectColumns.Where(i => i.SelectFromEntireRow)) - { - await selColumn.AddOrRemoveSelectedItemAsync(Item); - } + await column.OnRowClickAsync(row); } } } @@ -123,34 +122,43 @@ internal async Task HandleOnRowClickAsync(string rowId) /// internal async Task HandleOnRowDoubleClickAsync(string rowId) { - if (Owner.Rows.TryGetValue(rowId, out var row)) + var row = GetRow(rowId); + if (row != null && Owner.Grid.OnRowDoubleClick.HasDelegate) { - if (Owner.Grid.OnRowDoubleClick.HasDelegate) - { - await Owner.Grid.OnRowDoubleClick.InvokeAsync(row); - } + await Owner.Grid.OnRowDoubleClick.InvokeAsync(row); } } /// internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) { - // Enter when a SelectColumn is defined. - if (SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + { + return; + } + + var row = GetRow(rowId, r => r.RowType == DataGridRowType.Default); + if (row != null) { - if (Owner.Rows.TryGetValue(rowId, out var row)) + foreach (var column in Owner.Grid._columns) { - if (row != null && row.RowType == DataGridRowType.Default) - { - foreach (var selColumn in Owner.Grid.SelectColumns) - { - await selColumn.AddOrRemoveSelectedItemAsync(Item); - } - } + await column.OnRowKeyDownAsync(row, e); } } } + private FluentDataGridRow? GetRow(string rowId, Func, bool>? where = null) + { + if (!string.IsNullOrEmpty(rowId) && Owner.Rows.TryGetValue(rowId, out var row)) + { + return where == null + ? row + : row is not null && where(row) ? row : null; + } + + return null; + } + /// Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);