diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index 922f2f9c2e..adf9db9f59 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -1891,6 +1891,16 @@ A default fragment is used if loading content is not specified. + + + Sets to automatically fit the columns to the available width as best it can. + + + + + Gets the first (optional) SelectColumn + + Constructs an instance of . diff --git a/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor b/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor index 9698a7fdc2..a0c6b2f6c8 100644 --- a/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor +++ b/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor @@ -299,13 +299,41 @@ fluent-data-grid-row:has([row-selected]) { Example of using an outside div and the Style parameter to achieve a table like DataGrid with infinite horizontal scrollbars to display all content on all devices.

- If you use this in combination with a sticky header, the style of the header will be lost for the columns that are renderded out of the view initially. + If you use this in combination with a sticky header, the style of the header will be lost for the columns that are rendered out of the view initially. You can fix this by adding the following Style to your data grid: Style="min-width: max-content;"

+ + +

+ The example and code below show what you need to add to one of your Blazor page components to implement auto-fit. +

+

+ The AutoFit parameter is used to automatically adjust the column widths to fit the content. It only runs on + the first render and does not update when the content changes. +

+

+ The column widths are calculated with the process below: +

    +
  • + Loop through the columns and find the biggest width of each cell of the column +
  • +
  • + Build the GridTemplateColumns string using the fr unit +
  • +
+

+

+ This does not work + when Virtualization is turned on. The GridTemplateColumns parameter is ignored + when AutoFit is set to true. This is untested in MAUI. +

+
+
+

Documentation

diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor new file mode 100644 index 0000000000..c3bf9a049f --- /dev/null +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor @@ -0,0 +1,57 @@ +@using System.ComponentModel.DataAnnotations + + + +

With auto-fit

+ + + + + + +
+ +

Without auto-fit

+ + + + + + +
+
+ +@code { + public class Person + { + public Person(int personId, string name, DateOnly birthDate, string bio) + { + PersonId = personId; + Name = name; + BirthDate = birthDate; + Bio = bio; + } + + [Display(Name = "Identity")] + public int PersonId { get; set; } + + [Display(Name = "Name")] + public string Name { get; set; } + + [Display(Name = "Birth date")] + public DateOnly BirthDate { get; set; } + + [Display(Name = "Biography")] + public string Bio { get; set; } + } + + IQueryable people = new[] + { + new Person(10895, "Jean Martin", new DateOnly(1825, 11, 29), "Born on November 29, 1825, in Paris, France, is renowned as the founder of modern neurology."), + new Person(10944, "António Langa", new DateOnly(1972, 5, 15), "Born on May 15, 1972, in Columbia, South Carolina, is a distinguished former professional basketball player."), + new Person(11203, "Julie Smith", new DateOnly(1944, 11, 25), "Born on November 25, 1944, in Annapolis, Maryland, is an acclaimed American mystery writer celebrated for her rich storytelling."), + new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27), "Nur Sari is a fictional character known for her extraordinary contributions to the field of renewable energy."), + new Person(11898, "Jose Hernandez", new DateOnly(1962, 8, 7), "Born on August 7, 1962, in French Camp, California, is a Mexican-American engineer."), + new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9), ""), + }.AsQueryable(); +} diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor index 6f1f1b41df..bc357fb780 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -126,7 +126,6 @@ } - private void RenderColumnHeaders(RenderTreeBuilder __builder) { @for (var colIndex = 0; colIndex < _columns.Count; colIndex++) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 171c84080f..b6574581c1 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -8,6 +8,7 @@ using Microsoft.FluentUI.AspNetCore.Components.Extensions; using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; using Microsoft.JSInterop; + using System.Diagnostics.CodeAnalysis; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -215,6 +216,17 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Parameter] public RenderFragment? LoadingContent { get; set; } + /// + /// Sets to automatically fit the columns to the available width as best it can. + /// + [Parameter] + public bool AutoFit { get; set; } + + /// + /// Gets the first (optional) SelectColumn + /// + internal IEnumerable> SelectColumns => _columns.Where(col => col is SelectColumn).Cast>(); + private ElementReference? _gridReference; private Virtualize<(int, TGridItem)>? _virtualizeComponent; @@ -293,7 +305,14 @@ protected override void OnInitialized() /// protected override Task OnParametersSetAsync() { - _internalGridTemplateColumns = GridTemplateColumns; + if (AutoFit) + { + _internalGridTemplateColumns = "auto-fit"; + } + else + { + _internalGridTemplateColumns = GridTemplateColumns; + } // The associated pagination state may have been added/removed/replaced _currentPageItemsChanged.SubscribeOrMove(Pagination?.CurrentPageItemsChanged); @@ -337,6 +356,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { Console.WriteLine("[FluentDataGrid] " + ex.Message); } + + if (AutoFit && _gridReference is not null) + { + _ = Module?.InvokeVoidAsync("autoFitGridColumns", _gridReference, _columns.Count).AsTask(); + } } if (_checkColumnOptionsPosition && _displayOptionsForColumn is not null) @@ -420,12 +444,7 @@ public Task SortByColumnAsync(string title, SortDirection direction = SortDirect { var column = _columns.FirstOrDefault(c => c.Title?.Equals(title, StringComparison.InvariantCultureIgnoreCase) ?? false); - if (column is not null) - { - return SortByColumnAsync(column, direction); - } - - return Task.CompletedTask; + return column is not null ? SortByColumnAsync(column, direction) : Task.CompletedTask; } /// @@ -435,12 +454,7 @@ public Task SortByColumnAsync(string title, SortDirection direction = SortDirect /// The direction of sorting. The default is . If the value is , then it will toggle the direction on each call. public Task SortByColumnAsync(int index, SortDirection direction = SortDirection.Auto) { - if (index >= 0 && index < _columns.Count) - { - return SortByColumnAsync(_columns[index], direction); - } - - return Task.CompletedTask; + return index >= 0 && index < _columns.Count ? SortByColumnAsync(_columns[index], direction) : Task.CompletedTask; } /// @@ -622,14 +636,13 @@ private string AriaSortValue(ColumnBase column) private string? GridClass() { var value = $"{Class} {(_pendingDataLoadCancellationTokenSource is null ? null : "loading")}".Trim(); - if (string.IsNullOrEmpty(value)) - { - return null; - } - else + + if (AutoFit) { - return value; + value += " auto-fit"; } + + return string.IsNullOrEmpty(value) ? null : value; } private static string? ColumnClass(ColumnBase column) => column.Align switch diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index b0f0334439..28c17768ab 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -5,7 +5,7 @@ fluent-data-grid { ::deep .empty-content-row, ::deep .loading-content-row { width: 100%; - height: 100% !important ; + height: 100% !important; display: flex; justify-content: center; align-items: center; @@ -13,7 +13,6 @@ fluent-data-grid { ::deep .empty-content-cell, ::deep .loading-content-cell { - font-weight: 600; } @@ -55,7 +54,6 @@ fluent-data-grid { align-items: center; } - ::deep .col-width-draghandle { height: auto; width: calc(var(--design-unit) * 1px + 2px); diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.js b/src/Core/Components/DataGrid/FluentDataGrid.razor.js index d43e3c046b..2991e70d07 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.js +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.js @@ -91,7 +91,6 @@ export function checkColumnOptionsPosition(gridElement) { } } - export function enableColumnResizing(gridElement) { if (gridElement === latestGridElement) return; @@ -159,7 +158,7 @@ export function enableColumnResizing(gridElement) { } export function resetColumnWidths(gridElement) { - + gridElement.gridTemplateColumns = initialColumnsWidths; } @@ -216,6 +215,25 @@ export function resizeColumnDiscrete(gridElement, column, change) { .join(' '); } +export function autoFitGridColumns(gridElement, columnCount) { + let gridTemplateColumns = ''; + + for (var i = 0; i < columnCount; i++) { + const columnWidths = Array + .from(gridElement.querySelectorAll(`[grid-column="${i + 1}"]`)) + .flatMap((x) => x.offsetWidth); + + const maxColumnWidth = Math.max(...columnWidths); + + gridTemplateColumns += ` ${maxColumnWidth}fr`; + } + + gridElement.setAttribute("grid-template-columns", gridTemplateColumns); + gridElement.classList.remove("auto-fit"); + + initialColumnsWidths = gridTemplateColumns; +} + export function resizeColumnExact(gridElement, column, width) { let headers = gridElement.querySelectorAll('.column-header.resizable'); diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css index 0b71c25e35..3bb056b485 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -2,6 +2,12 @@ fluent-data-grid-cell { text-overflow: ellipsis; } +.auto-fit fluent-data-grid-cell { + text-overflow: unset; + overflow: visible; + visibility: hidden; +} + .multiline-text { white-space: inherit; overflow: auto; @@ -18,14 +24,12 @@ fluent-data-grid-cell { } .column-header.col-justify-end, .column-header.col-justify-right { - padding-right: 1px; padding-left: 1px; justify-content: end; } .column-header.col-justify-center { - padding-left: 1px; padding-right: 1px; justify-content: center; @@ -36,20 +40,20 @@ fluent-data-grid-cell { flex-grow: 1; } - .column-header > ::deep .keycapture .col-sort-container { - display: flex; - flex-grow: 1; - } + .column-header > ::deep .keycapture .col-sort-container { + display: flex; + flex-grow: 1; + } - .column-header > ::deep .keycapture .col-sort-container .col-sort-button { - flex-grow: 1; - padding-inline-end: 5px; - } + .column-header > ::deep .keycapture .col-sort-container .col-sort-button { + flex-grow: 1; + padding-inline-end: 5px; + } - .column-header > ::deep .keycapture .col-sort-container .col-sort-button::part(content) { - overflow: hidden; - text-overflow: ellipsis; - } + .column-header > ::deep .keycapture .col-sort-container .col-sort-button::part(content) { + overflow: hidden; + text-overflow: ellipsis; + } .column-header.col-justify-end ::deep .col-title-text, .column-header.col-justify-right ::deep .col-title-text { text-align: end; @@ -109,6 +113,3 @@ fluent-data-grid-cell { white-space: nowrap; font-weight: 600; } - - -